在使用块时发生了对 `self` 的循环引用。
在使用块时发生了对 `self` 的循环引用。
这个问题很基础,但我认为对很多开始使用blocks的Objective-C程序员来说是相关的。
我听说blocks会将引用其中的局部变量以const副本的形式捕获,所以在block中使用self可能会导致循环引用,如果block被复制的话。因此,我们应该使用__block来强制block直接处理self,而不是将其复制。
__block typeof(self) bself = self;
[someObject messageWithBlock:^{ [bself doSomething]; }];
而不是只是
[someObject messageWithBlock:^{ [self doSomething]; }];
我想知道的是:如果这是真的,除了使用GC,是否有其他方法可以避免这种丑陋的写法?
保留在`self`上使用块的循环出现的原因是因为块会持有其捕获的对象,如果对象又持有块,就会导致循环引用。为了解决这个问题,可以使用弱引用来避免循环引用。
在上述代码中,我们使用`__weak`修饰符来创建一个弱引用`weakSelf`,然后在块中使用该弱引用来调用`someOtherMethod`方法。这样做的好处是,如果`weakSelf`变为`nil`,则在调用`[weakSelf someOtherMethod]`时不会执行任何操作,因为向`nil`发送消息在Objective-C中是一个空操作。这样就避免了循环引用的问题。
使用弱引用的方法可以确保在块执行之后,如果`self`已经被释放,块不会再持有`self`,从而避免了循环引用。通过使用`__weak`修饰符,我们只是让`self`持有块,而块只接收`self`的弱引用。当块实际运行时,弱引用可以自动置空。
如果尝试在`someOtherMethod`中引用`self`,将会得到Xcode的警告。这是因为我们在块中直接引用`self`时会有循环引用的潜在问题。但是在`someOtherMethod`中使用`self`不会引起警告。这可能是因为Xcode无法智能地识别这种情况,或者这并不是一个问题。在`someOtherMethod`中引用`self`将指向`weakSelf`,因为我们是在`weakSelf`上调用该方法。
总之,解决在`self`上使用块的循环引用问题的方法就是使用弱引用。通过将`self`的弱引用传递给块,并在块中使用弱引用来调用方法,可以避免循环引用的发生。这样做可以确保在块执行之后,如果`self`已经被释放,块不会再持有`self`,从而解决了循环引用的问题。
如果想了解更多关于在实践中使用块和Grand Central Dispatch的信息,可以参考WWDC 2011的相关视频。
在使用blocks时,会存在对self的retain cycle问题。blocks在创建时会retain任何被捕获的Obj-C值。解决这个问题的方法是使用__block存储类作为变量的修饰符。一种解决方法是将self的值作为参数传递给block。然而,对于大多数API来说这并不适用。
另外,引用一个ivar也会有相同的问题。如果需要在block中引用ivar,可以使用属性或者bself->ivar。
补充:在使用ARC编译时,__block不再造成retain cycle。如果使用ARC编译,需要使用__weak或__unsafe_unretained。
解决retain cycle的另一种方法是使用__unsafe_unretained修饰符。如果确定对象在block执行时不会超出作用域,可以使用__unsafe_unretained,这样会稍微快一些。如果使用__weak,需要将其放入__strong的局部变量中,并在使用之前判断是否为nil。
__block的副作用是不会对值进行retain和release操作。在ARC中,编译器可以对其进行retain和release操作。如果需要避免这一点,需要使用__unsafe_unretained。
在Xcode 5.1与iOS7 SDK中,为什么仍然推荐使用__block来避免retain cycle?在非ARC代码中,使用__block仍然是避免对捕获的对象进行retain的正确方式。
关于retain cycle的问题,当block完成时,它会释放self。但是block会在block本身被释放时而不是执行完毕时释放self。如果block被self所保留(直接或间接地),且没有任何打破循环的操作,那么self和block会无限期地保持互相引用。
在OP的例子中,block是否会被self所保留(直接或间接地)?我假设只有当block被作为strong属性存储在self中时,block才会被self所保留(在这个例子中并不是这样)。还有其他方式使得self保留block的吗?
如果不使用iVar而是使用下划线,是否可以解决引用问题?
在使用block时,如果出现了循环引用的问题,可以通过对self进行别名处理来解决。当block作为一个回调接口时,循环引用的问题就会出现。例如,在下面的代码中:
typedef void (^BufferCallback)(FullBuffer* buffer); AudioProcessor : NSObject { … (copy) BufferCallback bufferHandler; } AudioProcessor - (id) init { … [self setBufferCallback:^(FullBuffer* buffer) { [self whatever]; }]; … }
在这种情况下,API的设计可能并不合理,但是当与超类进行通信时可能会有意义。我们对buffer handler进行了retain,而buffer handler也对self进行了retain。与下面的代码进行对比:
typedef void (^Callback)(void); VideoEncoder : NSObject { … } - (void) encodeVideoAndCall: (Callback) block; Foo : NSObject { … (retain) VideoEncoder *encoder; } Foo - (void) somewhere { [encoder encodeVideoAndCall:^{ [self doSomething]; }]; }
在这些情况下,我不会对self进行别名处理。虽然会出现循环引用,但是操作的持续时间很短,block最终会被释放,从而打破循环引用。但是我的block经验很少,可能在长期的实践中,对self进行别名处理会成为最佳实践。
好的观点。只有当self保持block存活时,才会出现循环引用。对于从未被复制的block,或者拥有有限持续时间的block(例如UIView动画的完成块),就不需要担心循环引用的问题。
原则上,你是对的。然而,如果你执行示例中的代码,你会崩溃。block属性应该始终声明为copy,而不是retain。如果它们只是retain,则不能保证它们会从堆栈中移出,这意味着当你执行时,它将不再存在。(复制已经被优化为retain)
是的,确实,这是一个打字错误。我在一段时间前经历了retain阶段,并很快意识到了你所说的这个问题。谢谢!
我相当确定retain在block中是被完全忽略的(除非它们已经通过copy从堆栈中移出)。在这里使用retain根本没有必要。
DeLong,不,它不会崩溃,因为(retain)仅用于对象引用,而不是block。这里根本不需要使用copy。