用块保留“自我”的循环

恐怕这个问题是非常基本的,但我认为这与很多进入块的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将保留。