[英]Retain cycle on `self` with blocks
我担心这个问题非常基本,但我认为这与很多进入数据块的Objective-C程序员有关。
我听到的是,因为块捕获它们作为const
副本引用的局部变量,所以在块中使用self
可以导致保留周期,如果该块被复制。 所以,我们应该使用__block
来强制块直接处理self
而不是复制它。
__block typeof(self) bself = self;
[someObject messageWithBlock:^{ [bself doSomething]; }];
而不仅仅是
[someObject messageWithBlock:^{ [self doSomething]; }];
我想知道的是:如果这是真的,有没有办法可以避免丑陋(除了使用GC)?
严格来说,它是一个const副本的事实与这个问题无关。 块将保留创建时捕获的任何obj-c值。 恰好,const-copy问题的解决方法与保留问题的解决方法相同; 即,使用__block
存储类作为变量。
无论如何,要回答你的问题,这里没有真正的选择。 如果你正在设计自己的基于块的API,并且这样做是有意义的,你可以让块作为参数传递self
in的值。 不幸的是,这对大多数API来说没有意义。
请注意,引用ivar具有完全相同的问题。 如果您需要在块中引用ivar,请使用属性代替或使用bself->ivar
。
附录:当编译为ARC时, __block
不再中断保留周期。 如果您正在为ARC编译,则需要使用__weak
或__unsafe_unretained
。
只需使用:
__weak id weakSelf = self;
[someObject someMethodWithBlock:^{
[weakSelf someOtherMethod];
}];
欲了解更多信息:WWDC 2011 - Block and Grand Central Dispatch in Practice 。
https://developer.apple.com/videos/wwdc/2011/?id=308
注意:如果这不起作用,您可以尝试
__weak typeof(self)weakSelf = self;
这可能是显而易见的,但是当你知道你会得到一个保留周期时,你只需要做一个丑陋的self
别名。 如果块只是一次性的东西,那么我认为你可以放心地忽略self
保留。 例如,当您将块作为回调接口时,不好的情况就是如此。 像这儿:
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;
}
object which then retains self
. 还要记住,如果您的块引用保留self
对象,则可能会发生保留周期。
我不确定垃圾收集可以帮助这些保留周期。 如果保留块的对象(我称之为服务器对象)超过self
(客户端对象),则在释放保留对象本身之前,块内对self
的引用将不会被视为循环。 如果服务器对象远远超过其客户端,则可能会发生严重的内存泄漏。
由于没有干净的解决方案,我建议采用以下解决方法。 随意选择其中一个或多个来解决您的问题。
doSomethingAndWhenDoneExecuteThisBlock:
等方法使用块,而不使用setNotificationHandlerBlock:
等方法。 用于完成的块具有明确的生命终结,并且应在评估后由服务器对象释放。 这可以防止保留周期长时间存活,即使它发生。 dealloc
而不是release
。 如果要编写服务器对象,请仅使用块参数完成。 不接受回调的块参数,例如setEventHandlerBlock:
. 相反,回到经典的委托模式:创建一个正式的协议,并宣传一个setEventDelegate:
方法。 不要保留代表。 如果您甚至不想创建正式协议,请接受选择器作为委托回调。
最后,这种模式应响铃:
- (void)dealloc { [myServerObject releaseCallbackBlocksForObject:self]; ... }
如果你试图解开可能从dealloc
内部引用self
块,那你就已经遇到了麻烦。 由于块中的引用引起的保留周期, dealloc
可能永远不会被调用,这意味着您的对象只是泄漏,直到服务器对象被释放。
在Kevin的帖子中建议的__block __unsafe_unretained
修饰符可能导致在不同线程中执行块的情况下的错误访问异常。 最好只使用__block修饰符作为临时变量,并在使用后使其为零。
__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库。 它很受欢迎,例如在ReactiveCocoa中使用它。 https://github.com/jspahrsummers/libextobjc
它提供了2个宏@weakify和@strongify,因此您可以:
@weakify(self)
[someObject messageWithBlock:^{
@strongify(self)
[self doSomething];
}];
这可以防止直接强引用,因此我们不会进入自我保留周期。 而且,它会阻止self在中途变为零,但仍然可以正确地减少保留计数。 更多链接: 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];
}
...
}
我不再收到编译器警告了。
块:将发生保留周期,因为它包含块中引用的块; 如果您进行块复制并使用成员变量,则self将保留。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.