用块在“self”上保留循环
恐怕这个问题非常基础,但我认为它与许多陷入困境的 Objective-C 程序员相关。
我听说的是,由于块捕获在其中引用的局部变量作为 const 副本,因此在块中使用 self 可能会导致保留周期(如果该块被复制) 。因此,我们应该使用__block
来强制块直接处理self
而不是复制它。
__block typeof(self) bself = self;
[someObject messageWithBlock:^{ [bself doSomething]; }];
而不仅仅是
[someObject messageWithBlock:^{ [self doSomething]; }];
我想知道的是:如果这是真的,有没有办法可以避免这种丑陋(除了使用 GC 之外)?
I'm afraid this question is pretty basic, but I think it's relevant to a lot of Objective-C programmers who are getting into blocks.
What I've heard is that since blocks capture local variables referenced within them as const
copies, using self
within a block can result in a retain cycle, should that block be copied. So, we are supposed to use __block
to force the block to deal directly with self
instead of having it copied.
__block typeof(self) bself = self;
[someObject messageWithBlock:^{ [bself doSomething]; }];
instead of just
[someObject messageWithBlock:^{ [self doSomething]; }];
What I'd like to know is the following: if this is true, is there a way that I can avoid the ugliness (aside from using GC)?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
严格来说,它是一个 const 副本这一事实与这个问题无关。块将保留创建时捕获的任何 obj-c 值。碰巧 const-copy 问题的解决方法与保留问题的解决方法相同;即,使用变量的
__block
存储类。无论如何,为了回答你的问题,这里没有真正的选择。如果您正在设计自己的基于块的 API,并且这样做是有意义的,那么您可以让块将
self
的值作为参数传递。不幸的是,这对于大多数 API 来说没有意义。请注意,引用 ivar 也有完全相同的问题。如果您需要在块中引用 ivar,请使用属性或使用
bself->ivar
。附录:当编译为 ARC 时,
__block
不再破坏保留循环。如果您要针对 ARC 进行编译,则需要使用__weak
或__unsafe_unretained
。Strictly speaking, the fact that it's a const copy has nothing to do with this problem. Blocks will retain any obj-c values that are captured when they are created. It just so happens that the workaround for the const-copy issue is identical to the workaround for the retain issue; namely, using the
__block
storage class for the variable.In any case, to answer your question, there's no real alternative here. If you're designing your own block-based API, and it makes sense to do so, you could have the block get passed the value of
self
in as an argument. Unfortunately, this doesn't make sense for most APIs.Please note that referencing an ivar has the exact same issue. If you need to reference an ivar in your block, either use a property instead or use
bself->ivar
.Addendum: When compiling as ARC,
__block
no longer breaks retain cycles. If you're compiling for ARC, you need to use__weak
or__unsafe_unretained
instead.只需使用:
了解更多信息:WWDC 2011 - Blocks 和 Grand Central Dispatch 实践。
https://developer.apple.com/videos/wwdc/2011/?id =308
注意:如果这不起作用,您可以尝试
Just use:
For more information: WWDC 2011 - Blocks and Grand Central Dispatch in Practice.
https://developer.apple.com/videos/wwdc/2011/?id=308
Note: if that doesn't work you can try
这可能是显而易见的,但当您知道将获得保留周期时,您只需使用丑陋的
self
别名即可。如果该块只是一次性的事情,那么我认为您可以安全地忽略self
上的保留。例如,糟糕的情况是当您将块作为回调接口时。就像这里:这里 API 没有多大意义,但例如在与超类通信时它就有意义。我们保留缓冲区处理程序,缓冲区处理程序保留我们。与这样的情况进行比较:
在这些情况下,我不使用
self
别名。您确实获得了保留周期,但该操作是短暂的,并且该块最终将耗尽内存,从而打破循环。但我对块的经验非常少,从长远来看,自我别名可能是最佳实践。This might be obvious, but you only have to do the ugly
self
alias when you know you’ll get a retain cycle. If the block is just a one-shot thing then I think you can safely ignore the retain onself
. The bad case is when you have the block as a callback interface, for example. Like here:Here the API does not make much sense, but it would make sense when communicating with a superclass, for example. We retain the buffer handler, the buffer handler retains us. Compare with something like this:
In these situations I don’t do the
self
aliasing. You do get a retain cycle, but the operation is short-lived and the block will get out of memory eventually, breaking the cycle. But my experience with blocks is very small and it might be thatself
aliasing comes out as a best practice in the long run.发布另一个答案,因为这对我来说也是一个问题。我最初认为我必须在块内有自引用的任何地方使用 blockSelf。事实并非如此,只有当对象本身有一个块时才会出现这种情况。事实上,如果您在这些情况下使用 blockSelf,该对象可能会在您从块返回结果之前被释放,然后当它尝试调用它时它会崩溃,所以显然您希望 self 被保留直到响应回来了。
第一种情况演示了何时会发生保留循环,因为它包含一个在该块中引用的块:
在第二种情况下,您不需要 blockSelf,因为调用对象中没有块,当您调用该对象时,将导致保留循环参考自:
Posting another answer because this was a problem for me too. I originally thought I had to use blockSelf anywhere there was a self reference inside a block. This is not the case, it is only when the object itself has a block in it. And in fact, if you use blockSelf in these cases the object can get dealloc'd before you get the result back from the block and then it will crash when it tries to call it, so clearly you want self to be retained until the response comes back.
First case demonstrates when a retain cycle will occur because it contains a block which is referenced in the block:
You don't need blockSelf in the second case because the calling object does not have a block in it that will cause a retain cycle when you reference self:
还要记住,如果您的块引用了另一个对象,然后该对象又保留了
self
,则可能会发生保留循环。我不确定垃圾收集可以在这些保留周期中提供帮助。如果保留块的对象(我将其称为服务器对象)比 self(客户端对象)长,则块内对 self 的引用将不会被视为循环直到保留对象本身被释放。如果服务器对象的寿命远远超过其客户端,则可能会出现严重的内存泄漏。
由于没有干净的解决方案,我建议使用以下解决方法。请随意选择其中一个或多个来解决您的问题。
doSomethingAndWhenDoneExecuteThisBlock:
等方法,而不是setNotificationHandlerBlock:
等方法。用于完成的块有明确的生命周期,并且应该在评估后由服务器对象释放。这可以防止保留周期即使发生也不会存在太久。dealloc
而不是release
。如果您正在编写服务器对象,请仅在完成时使用块参数。不接受回调的块参数,例如
setEventHandlerBlock:
。相反,回到经典的委托模式:创建一个正式的协议,并通告一个setEventDelegate:
方法。不要保留代表。如果您甚至不想创建正式协议,请接受选择器作为委托回调。最后,这种模式应该敲响警钟:
如果您试图从
dealloc
内部取消可能引用self
的块,那么您已经遇到麻烦了。由于块中的引用引起的保留循环,dealloc 可能永远不会被调用,这意味着您的对象只会泄漏,直到服务器对象被释放为止。Remember also that retain cycles can occur if your block refers to another object which then retains
self
.I'm not sure that Garbage Collection can help in these retain cycles. If the object retaining the block (which I'll call the server object) outlives
self
(the client object), the reference toself
inside the block will not be considered cyclic until the retaining object itself is released. If the server object far outlives its clients, you may have a significant memory leak.Since there are no clean solutions, I would recommend the following workarounds. Feel free to choose one or more of them to fix your issue.
doSomethingAndWhenDoneExecuteThisBlock:
, and not methods likesetNotificationHandlerBlock:
. Blocks used for completion have definite ends of lives, and should be released by server objects after they are evaluated. This prevents the retain cycle from living for too long even if it occurs.dealloc
instead ofrelease
.If you are writing a server object, take block arguments only for completion. Do not accept block arguments for callbacks, such as
setEventHandlerBlock:
. Instead, fall back to the classic delegate pattern: create a formal protocol, and advertise asetEventDelegate:
method. Do not retain the delegate. If you don't even want to create a formal protocol, accept a selector as a delegate callback.And lastly, this pattern should ring alarms:
If you're trying to unhook blocks that may refer to
self
from insidedealloc
, you're already in trouble.dealloc
may never be called due to the retain cycle caused by references in the block, which means that your object is simply going to leak until the server object is deallocated.Kevin 的帖子中建议的
__block __unsafe_unretained
修饰符可能会在块执行的情况下导致错误的访问异常在不同的线程中。最好只对 temp 变量使用 __block 修饰符,并在使用后将其设置为 nil。__block __unsafe_unretained
modifiers suggested in Kevin's post may cause to the bad access exception in case of block executed in a different thread. It's better use only __block modifier for the temp variable and make it nil after the usage.您可以使用 libextobjc 库。它非常流行,例如 ReactiveCocoa 中就使用了它。
https://github.com/jspahrsummers/libextobjc
它提供了2个宏@weakify和@strongify,所以你可以有:
这可以防止直接强引用,这样我们就不会进入 self 的保留循环。而且,它可以防止 self 中途变为 nil,但仍然适当地减少保留计数。
更多内容请参见此链接:
http://aceontech .com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html
You can use libextobjc library. It is quite popular, it is used in ReactiveCocoa for example.
https://github.com/jspahrsummers/libextobjc
It provides 2 macros @weakify and @strongify, so you can have:
This prevents a direct strong reference so we don't get into a retain cycle to self. And also, it prevents self from becoming nil half-way, but still properly decrements the retain count.
More in this link:
http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html
这个怎么样?
我不再收到编译器警告。
How about this?
I don't get the the compiler warning anymore.
Block:因为包含了一个block,并且在该block中引用了,所以会发生retain Cycle;
如果您进行块复制并使用成员变量,则 self 将保留。
Block: a retain cycle will occur because it contains a block which is referenced in the block;
If you make the block copy and use a member variable,self will retain.