Retain cycle on `self` with blocks
恐怕这个问题很基本,但我认为它与许多进入模块的客观C程序员有关。
我听到的是,由于块捕获其中引用的局部变量作为
1 2 | __block typeof(self) bself = self; [someObject messageWithBlock:^{ [bself doSomething]; }]; |
而不仅仅是
1 | [someObject messageWithBlock:^{ [self doSomething]; }]; |
我想知道的是:如果这是真的,有没有一种方法可以避免这种丑陋(除了使用GC之外)?
严格地说,这是一份副本与这个问题无关。块将保留创建时捕获的所有obj-c值。正因为如此,const copy问题的变通方法与retain问题的变通方法相同,即使用
无论如何,要回答你的问题,这里没有真正的选择。如果您正在设计自己的基于块的API,并且这样做是有意义的,那么可以将该块作为参数传入
请注意,引用IVAR的问题完全相同。如果需要引用块中的ivar,请改用属性或使用
附录:当编译为ARC时,
只需使用:
1 2 3 4 5 | __weak id weakSelf = self; [someObject someMethodWithBlock:^{ [weakSelf someOtherMethod]; }]; |
更多信息:WWDC 2011-实际中的区块和大中心调度。
https://developer.apple.com/videos/wwdc/2011/?ID=308
注意:如果这不起作用,你可以试试。
1 | __weak typeof(self)weakSelf = self; |
这可能很明显,但您只需在知道将获得保留周期时使用难看的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | typedef void (^BufferCallback)(FullBuffer* buffer); @interface AudioProcessor : NSObject {…} @property(copy) BufferCallback bufferHandler; @end @implementation AudioProcessor - (id) init { … [self setBufferCallback:^(FullBuffer* buffer) { [self whatever]; }]; … } |
在这里,API没有多大意义,但是在与超类通信时,它是有意义的,例如。我们保留缓冲处理程序,缓冲处理程序保留我们。与以下内容比较:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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]; }]; } |
在这些情况下,我不进行
发布另一个答案,因为这对我来说也是个问题。我原以为我必须在一个块内有自引用的地方使用block self。不是这样的,只有当对象本身有一个块在其中时。事实上,如果您在这些情况下使用block self,对象可以在从块中得到结果之前得到释放,然后当它试图调用它时,它将崩溃,所以很明显您希望保留self直到响应返回。
第一种情况演示了由于包含块中引用的块而发生保留循环的时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | #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]; } |
在第二种情况下,您不需要block self,因为调用对象中没有块,当您引用self时,它将导致保留周期:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | #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; } |
还请记住,如果块引用另一个对象,然后保留
我不确定垃圾收集在这些保留周期中是否有帮助。如果保留块的对象(我称之为服务器对象)超过了
由于没有干净的解决方案,我建议采用以下解决方法。请随意选择其中一个或多个解决您的问题。
- 只将块用于完成,而不用于开放式事件。例如,对方法(如
doSomethingAndWhenDoneExecuteThisBlock: )使用块,而不是对方法(如setNotificationHandlerBlock: )使用块。用于完成的块有明确的生命终点,在对它们进行评估之后,服务器对象应该释放这些块。这可以防止保留循环在发生时存活太久。 - 做你描述的弱参考舞蹈。
- 提供一个方法,在对象被释放之前将其清理干净,该方法将对象与可能包含对其引用的服务器对象"断开连接";并在对对象调用释放之前调用此方法。如果对象只有一个客户机(或者在某些上下文中是一个单实例),则此方法非常好,但是如果对象有多个客户机,则此方法将崩溃。你基本上是在破坏这里的保留计数机制;这类似于调用
dealloc ,而不是release 。
如果您正在编写一个服务器对象,那么只需要使用块参数来完成。不要接受回调的块参数,如
最后,该模式应发出警报:
1 2 3 4 | - (void)dealloc { [myServerObject releaseCallbackBlocksForObject:self]; ... } |
如果你试图从
您可以使用libextobjc库。它很受欢迎,例如,它被用在reactivevocoa中。https://github.com/jspahrsummers/libextobjc
它提供了2个宏@weakify和@strongify,因此您可以:
1 2 3 4 5 | @weakify(self) [someObject messageWithBlock:^{ @strongify(self) [self doSomething]; }]; |
这会阻止直接的强引用,因此我们不会进入自我保留循环。而且,它还可以防止自己中途变成零,但仍然适当地减少保留计数。此链接中的更多内容:http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegic-solution-to-weakself.html
Kevin的文章中建议的
1 2 3 4 5 6 | __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; }]; |
这个怎么样?
1 2 3 4 5 6 7 8 | - (void) foo { __weak __block me = self; myBlock = ^ { [[me someProp] someMessage]; } ... } |
我不再收到编译器警告了。
块:保留周期将发生,因为它包含块中引用的块;如果复制块并使用成员变量,则self将保留。