用块保留“自我”的循环
恐怕这个问题是非常基本的,但我认为这与很多进入块的Objective-C程序员有关。
我所听到的是,因为块捕获的局部variables被作为const
拷贝引用,所以如果块被复制,在块中使用self
可能会导致保留周期。 所以,我们应该使用__block
来强制块直接处理self
而不是复制它。
__block typeof(self) bself = self; [someObject messageWithBlock:^{ [bself doSomething]; }];
而不是仅仅
[someObject messageWithBlock:^{ [self doSomething]; }];
我想知道的是:如果这是真的,有没有一种方法可以避免丑(除了使用GC)?
严格来说,它是一个常量,与这个问题无关。 块将保留创build时捕获的任何obj-c值。 恰恰恰巧,const-copy问题的解决方法与保留问题的解决方法相同; 即使用__block
存储类作为variables。
无论如何,回答你的问题,这里没有真正的select。 如果您正在devise自己的基于块的API,并且这样做是有道理的,那么您可以让块通过self
的值作为参数。 不幸的是,这对大多数API来说都没有意义。
请注意,引用伊娃有完全相同的问题。 如果您需要在您的区块中引用bself->ivar
,请改用属性或使用bself->ivar
。
附录:当编译为ARC时, __block
不再中断保留周期。 如果您正在编译ARC,则需要使用__weak
或__unsafe_unretained
。
只要使用:
__weak id weakSelf = self; [someObject someMethodWithBlock:^{ [weakSelf someOtherMethod]; }];
欲了解更多信息:WWDC 2011 – 块和大中央调度实践 。
https://developer.apple.com/videos/wwdc/2011/?id=308
注意:如果这不起作用,你可以尝试
__weak typeof(self)weakSelf = self;
这可能是显而易见的,但是当你知道你会得到一个保留周期的时候,你只需要做一个丑陋的self
别名。 如果该块只是一个镜头的东西,那么我认为你可以放心地忽略对self
的保留。 例如,当您将该块作为callback接口时,情况就不好了。 像这样:
typedef void (^BufferCallback)(FullBuffer* buffer); @interface AudioProcessor : NSObject {…} @property(copy) BufferCallback bufferHandler; @end @implementation AudioProcessor - (id) init { … [self setBufferCallback:^(FullBuffer* buffer) { [self whatever]; }]; … }
这里的API没有什么意义,但是当与一个超类进行通信时,这是有意义的。 我们保留缓冲区处理程序,缓冲区处理程序保留我们。 比较像这样的东西:
typedef void (^Callback)(void); @interface VideoEncoder : NSObject {…} - (void) encodeVideoAndCall: (Callback) block; @end @interface Foo : NSObject {…} @property(retain) VideoEncoder *encoder; @end @implementation Foo - (void) somewhere { [encoder encodeVideoAndCall:^{ [self doSomething]; }]; }
在这些情况下,我不做self
别名。 你确实得到了一个保留周期,但是这个操作是短暂的,这个块最终会失去内存,打破这个循环。 但是,我的经验是非常小的,从长远来看, self
走样可能是最好的做法。
发布另一个答案,因为这对我来说也是一个问题。 我原本以为我不得不使用blockSelf,在一个块内有一个自引用。 事实并非如此,只有当对象本身有一个块的时候。 实际上,如果在这些情况下使用blockSelf,那么在从块中得到结果之前,对象可以被解除分配,然后当它试图调用它的时候就会崩溃,所以显然你希望自我保留,直到响应回来。
第一种情况说明何时会发生保留周期,因为它包含块中引用的块:
#import <Foundation/Foundation.h> typedef void (^MyBlock)(void); @interface ContainsBlock : NSObject @property (nonatomic, copy) MyBlock block; - (void)callblock; @end @implementation ContainsBlock @synthesize block = _block; - (id)init { if ((self = [super init])) { //__block ContainsBlock *blockSelf = self; // to fix use this. self.block = ^{ NSLog(@"object is %@", self); // self retain cycle }; } return self; } - (void)dealloc { self.block = nil; NSLog (@"ContainsBlock"); // never called. [super dealloc]; } - (void)callblock { self.block(); } @end int main() { ContainsBlock *leaks = [[ContainsBlock alloc] init]; [leaks callblock]; [leaks release]; }
在第二种情况下,您不需要blockSelf,因为调用对象中没有块,当您引用self时将导致保留周期:
#import <Foundation/Foundation.h> typedef void (^MyBlock)(void); @interface BlockCallingObject : NSObject @property (copy, nonatomic) MyBlock block; @end @implementation BlockCallingObject @synthesize block = _block; - (void)dealloc { self.block = nil; NSLog(@"BlockCallingObject dealloc"); [super dealloc]; } - (void)callblock { self.block(); } @end @interface ObjectCallingBlockCallingObject : NSObject @end @implementation ObjectCallingBlockCallingObject - (void)doneblock { NSLog(@"block call complete"); } - (void)dealloc { NSLog(@"ObjectCallingBlockCallingObject dealloc"); [super dealloc]; } - (id)init { if ((self = [super init])) { BlockCallingObject *myobj = [[BlockCallingObject alloc] init]; myobj.block = ^() { [self doneblock]; // block in different object than this object, no retain cycle }; [myobj callblock]; [myobj release]; } return self; } @end int main() { ObjectCallingBlockCallingObject *myObj = [[ObjectCallingBlockCallingObject alloc] init]; [myObj release]; return 0; }
还请记住,如果您的块引用了另一个保留self
对象,则可能会发生保留循环。
我不确定垃圾收集可以帮助这些保留周期。 如果保留块(我将称之为服务器对象)的对象超出了self
(客户端对象)的范围,那么在释放保留对象本身之前,块内部的self
引用将不被视为循环。 如果服务器对象远远落后于客户端,则可能会出现严重的内存泄漏。
由于没有干净的解决scheme,我会推荐以下解决方法。 随意select一个或多个解决您的问题。
- 只能用块来完成 ,而不能用于开放式的事件。 例如,像
doSomethingAndWhenDoneExecuteThisBlock:
这样的方法使用块,而不是像setNotificationHandlerBlock:
这样的方法。 用于完成的块具有明确的生命终点,并且在评估之后应该由服务器对象释放。 这可以防止保留周期长时间存在。 - 你所描述的那个虚弱的参考舞蹈吗?
- 提供一种在释放对象之前清理对象的方法,该对象将服务器对象与可能拥有引用的对象“断开连接”; 并在调用对象释放之前调用此方法。 如果你的对象只有一个客户端(或者是某个上下文中的单例),那么这个方法是完全正确的,但是如果它有多个客户端的话,它将会崩溃。 你基本上打败了这里的保留计票机制。 这类似于调用
dealloc
而不是release
。
如果你正在编写一个服务器对象,那么只能使用块参数来完成。 不要接受callback的块参数,比如setEventHandlerBlock:
相反,回到典型的委托模式:创build一个正式的协议,并宣传一个setEventDelegate:
方法。 不要保留这个委托。 如果你甚至不想创build一个正式的协议,接受一个select器作为委托callback。
最后,这种模式应该会响起警报:
- (void)dealloc { [myServerObject releaseCallbackBlocksForObject:self]; ... }
如果你试图从dealloc
里面解开可能引用self
块,那么你已经陷入困境了。 dealloc
可能永远不会被调用,因为由块引用引起的保留周期,这意味着你的对象只是泄漏,直到服务器对象被释放。
在Kevin的post中提出的__block __unsafe_unretained
修饰符可能会导致在不同线程中执行block的情况下访问exception。 最好使用__block修饰符作为临时variables,并在使用之后使其成为零。
__block SomeType* this = self; [someObject messageWithBlock:^{ [this doSomething]; // here would be BAD_ACCESS in case of __unsafe_unretained with // multithreading and self was already released this = nil; }];
你可以使用libextobjc库。 这是相当stream行的,例如在ReactiveCocoa中使用。 https://github.com/jspahrsummers/libextobjc
它提供了2个macros@weakify和@strongify,所以你可以有:
@weakify(self) [someObject messageWithBlock:^{ @strongify(self) [self doSomething]; }];
这可以防止直接强烈的参考,所以我们不进入保留周期自我。 而且,这样可以防止自己成为零,但仍然适当地减less保留数量。 更多链接: http : //aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html
这个怎么样?
- (void) foo { __weak __block me = self; myBlock = ^ { [[me someProp] someMessage]; } ... }
我没有得到编译器警告了。
块:保留周期将发生,因为它包含块中引用的块; 如果你使用块复制并使用成员variables,self将保留。