How do I avoid capturing self in blocks when implementing an API?

Short answer

Instead of accessing self directly, you should access it indirectly, from a reference that will not be retained. If you're not using Automatic Reference Counting (ARC), you can do this:

__block MyDataProcessor*dp = self;
self.progressBlock =^(CGFloat percentComplete){[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];}

The __block keyword marks variables that can be modified inside the block (we're not doing that) but also they are not automatically retained when the block is retained (unless you are using ARC). If you do this, you must be sure that nothing else is going to try to execute the block after the MyDataProcessor instance is released. (Given the structure of your code, that shouldn't be a problem.) Read more about__block.

If you are using ARC, the semantics of __block changes and the reference will be retained, in which case you should declare it __weak instead.

Long answer

Let's say you had code like this:

self.progressBlock =^(CGFloat percentComplete){[self.delegate processingWithProgress:percentComplete];}

The problem here is that self is retaining a reference to the block; meanwhile the block must retain a reference to self in order to fetch its delegate property and send the delegate a method. If everything else in your app releases its reference to this object, its retain count won't be zero (because the block is pointing to it) and the block isn't doing anything wrong (because the object is pointing to it) and so the pair of objects will leak into the heap, occupying memory but forever unreachable without a debugger. Tragic, really.

That case could be easily fixed by doing this instead:

id progressDelegate = self.delegate;
self.progressBlock =^(CGFloat percentComplete){[progressDelegate processingWithProgress:percentComplete];}

In this code, self is retaining the block, the block is retaining the delegate, and there are no cycles (visible from here; the delegate may retain our object but that's out of our hands right now). This code won't risk a leak in the same way, because the value of the delegate property is captured when the block is created, instead of looked up when it executes. A side effect is that, if you change the delegate after this block is created, the block will still send update messages to the old delegate. Whether that is likely to happen or not depends on your application.

Even if you were cool with that behavior, you still can't use that trick in your case:

self.dataProcessor.progress =^(CGFloat percentComplete){[self.delegate myAPI:self isProcessingWithProgress:percentComplete];};

Here you are passing self directly to the delegate in the method call, so you have to get it in there somewhere. If you have control over the definition of the block type, the best thing would be to pass the delegate into the block as a parameter:

self.dataProcessor.progress =^(MyDataProcessor*dp,CGFloat percentComplete){[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];};

This solution avoids the retain cycle and always calls the current delegate.

If you can't change the block, you could deal with it. The reason a retain cycle is a warning, not an error, is that they don't necessarily spell doom for your application. If MyDataProcessor is able to release the blocks when the operation is complete, before its parent would try to release it, the cycle will be broken and everything will be cleaned up properly. If you could be sure of this, then the right thing to do would be to use a #pragma to suppress the warnings for that block of code. (Or use a per-file compiler flag. But don't disable the warning for the whole project.)

You could also look into using a similar trick above, declaring a reference weak or unretained and using that in the block. For example:

__weak MyDataProcessor*dp = self;// OK for iOS 5 only
__unsafe_unretained MyDataProcessor*dp = self;// OK for iOS 4.x and up
__block MyDataProcessor*dp = self;// OK if you aren't using ARC
self.progressBlock =^(CGFloat percentComplete){[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];}

All three of the above will give you a reference without retaining the result, though they all behave a little bit differently: __weak will try to zero the reference when the object is released;__unsafe_unretained will leave you with an invalid pointer; __block will actually add another level of indirection and allow you to change the value of the reference from within the block (irrelevant in this case, since dp isn't used anywhere else).

What's best will depend on what code you are able to change and what you cannot. But hopefully this has given you some ideas on how to proceed.


share|edit|flag

edited Nov 2 '11 at 1:38

 

 

answered Oct 21 '11 at 19:25

benzado
32.1k972100

 

1  
   

Awesome answer! Thanks, I have a much better understanding about what is going on and how this all works. In this case, I have control over everything so I'll re-architect some of the objects as needed. – XJones Oct 21 '11 at 21:18
   
   

Good answer. I've been using __weak myself in an iOS project. It gives you more safety than __block or __unsafe_unretained, but I imagine most people would be calling this in a situation where self is guaranteed to exist. I also like __weak as a keyword because it is more "explicit" about what's being accomplished, whereas the intentions of __block can be ambiguous. –  mikelikespie Nov 2 '11 at 1:15
7  
   

O_O I was just passing by with a slightly different problem, got stuck reading, and now leave this page feeling all knowledgeable and cool. Thanks! –  Orc JMR Dec 19 '12 at 8:07
   
   

@OrcJMR You're welcome! –  benzado Dec 19 '12 at 22:12
   
   

is is correct, that if for some reason on the moment of block execution dp will be released (for example if it was a view controller and it was poped), then line [dp.delegate ... will cause EXC_BADACCESS? – peetonn Jan 30 '13 at 1:50
   

add / show 5 more comments


up vote17down vote

There’s also the option to suppress the warning when you are positive that the cycle will get broken in the future:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"

self.progressBlock =^(CGFloat percentComplete){[self.delegate processingWithProgress:percentComplete];}#pragma clang diagnostic pop

That way you don’t have to monkey around with __weakself aliasing and explicit ivar prefixing.


share|edit|flag

answered Feb 14 '12 at 7:51

zoul
46.8k14112201

 

1  
   

Sounds like a very bad practice that takes more than 3 lines of code that can be replaced with __weak id weakSelf = self; –  Andy Sep 3 '13 at 14:05 
1  
   

There’s often a larger block of code that can benefit from the suppressed warnings. –  zoul Sep 3 '13 at 17:56
   

add comment


up vote7down vote

I believe the solution without ARC also works with ARC, using the __block keyword:

EDIT: Per the Transitioning to ARC Release Notes, an object declared with __block storage is still retained. Use __weak (preferred) or __unsafe_unretained (for backwards compatibility).

// code sample
self.delegate= aDelegate;

self.dataProcessor =[[MyDataProcessor alloc] init];// Use this inside blocks
__block id myself = self;

self.dataProcessor.progress =^(CGFloat percentComplete){[myself.delegate myAPI:myself isProcessingWithProgress:percentComplete];};

self.dataProcessor.completion =^{[myself.delegate myAPIDidFinish:myself];
    myself.dataProcessor = nil;};// start the processor - processing happens asynchronously and the processor is released in the completion block[self.dataProcessor startProcessing];

share|edit|flag

edited Oct 24 '11 at 15:22

 

 

answered Oct 21 '11 at 19:59

Tony
1,988711

 

   
   

Didn't realize that the __block keyword avoided retaining it's referent. Thanks! I updated my monolithic answer. :-) –  benzado Oct 21 '11 at 21:41
1  
   

According to Apple docs "In manual reference counting mode, __block id x; has the effect of not retaining x. In ARC mode, __block id x; defaults to retaining x (just like all other values)." –  XJones Oct 22 '11 at 2:08 
   
   

@XJones Thanks for the clarification, I've edited my answer. –  Tony Oct 24 '11 at 15:09
   

add comment


up vote6down vote

For a common solution, I have these define in the precompile header. Avoids capturing and still enables compiler help by avoiding to use id

#defineBlockWeakObject(o) __typeof(o) __weak
#defineBlockWeakSelfBlockWeakObject(self)

Then in code you can do:

BlockWeakSelf weakSelf = self;
self.dataProcessor.completion =^{[weakSelf.delegate myAPIDidFinish:weakSelf];
    weakSelf.dataProcessor = nil;};

欢迎加群互相学习,共同进步。QQ群:iOS: 58099570 | Android: 330987132 | Go:217696290 | Python:336880185 | 做人要厚道,转载请注明出处!

时间: 2024-10-31 04:02:02

How do I avoid capturing self in blocks when implementing an API?的相关文章

x264代码剖析(十八):核心算法之滤波

x264代码剖析(十八):核心算法之滤波           H.264/MPEG-4 AVC视频编码标准中,在编解码器反变换量化后,图像会出现方块效应,主要原因是:1)基于块的帧内和帧间预测残差的DCT变换,变换系数的量化过程相对粗糙,因而反量化过程恢复的变换系数有误差,会造成在图像块边界上的视觉不连续:2)运动补偿可能是从不是同一帧的不同位置上内插样点数据复制而来,因为运动补偿块的匹配不可能是绝对准确的,所以就会在复制块的边界上产生数据不连续:3)参考帧中的存在的不连续也被复制到需要补偿的图

JDBC技术介绍

1. 介 绍 Java 许多开发者和用户都在寻找程Java序中访问数据库的便捷方法.由于是download 一个健壮,安全,易于使用的,易于理解且可以从网络中自动,C所以它成为开发数据库应用的一种良好的语言基础.它提供了,C+Smalltalk+,, BASIC, COBOL, and 4GLs的Java许多优点.许多公司已经开始在与DBMS的 连接方面做工作. Java 许多应DBMS用开发者都希望能够编写独立于特定的DBMS程序,而我们也相信一个独立于的DBMS接口将使得与各种各样连SQL接

Data Blocks and Freelists (from ixora)

Questions and Answers Data Blocks and Freelists Transaction and process freelists 26 October 1998 You mentioned that there are different types of free lists. Could you please explain a bit more about all this?   Each segment has at least a master fre

code::blocks中把10^9定义成double型没有溢出定义成long double型却溢出

问题描述 code::blocks中把10^9定义成double型没有溢出定义成long double型却溢出 code::blocks(GNU GCC Complier)中把10^9定义成double型没有溢出,定义成long double型反倒溢出了.而10^9既没有超过double的范围,更没有超过Long double的范围,这是GNU GCC Complier的bug吗?我的code::blocks是16.01的,已经是最新版了. #include #include double a;

fortran-Code::Blocks怎样自动识别Fortran源码格式?

问题描述 Code::Blocks怎样自动识别Fortran源码格式? 本人菜鸟,在用Code::Blocks编译一个大型Fortran程序时遇到以下错误信息:-------------- Build: Debug in BernLib (compiler: PGI Fortran Compiler)--------------- pgfortran.exe -g -module objDebug -c ..LibBERNFORAMBCHN.f -o objDebugLibBERNFORAMBC

[翻译] Blocks and Variables

Blocks and Variables https://developer.apple.com/library/ios/documentation/cocoa/conceptual/Blocks/Articles/bxVariables.html http://stackoverflow.com/questions/16149653/what-is-the-role-of-the-copy-in-the-arc   This article describes the interaction

解决 The Controls collection cannot be modified because the control contains code blocks

在.aspx或.ascx的如果包括<%= %>,并在.aspx, .ascs中使用了AjaxToolkit中的控件,那么很可能会引发"The Controls collection cannot be modified because the control contains code blocks" 异常, 解决办法如下 1. 将<%= 换为<%# 2. 在.asp或模板页中的Page_Load事件中添加如下代码 复制代码 代码如下: Page.Header.

Linux下c++编译器Code::Blocks安装

最近想写写C .C++方面的程序,所以想找一个Linux下的编辑器来用用, 找了很多也试了不少,最后锁定了CodeBlocks.以下是关于他的介绍和一些安装 过程.适用所有的Linux吧.(有时就只是需要一个安装的思路,其它的都差不 多一样的) (摘抄)Code::Blocks,有时也简单打成 "CodeBlocks",是一款全新的C++集成设置环境软件(IDE). 作为一款C++的IDE,Code::Blocks至少有以下几点非常吸引我. 开源--开源不仅仅意味着免费,但就算是仅仅是

ubuntu 11.04下如何汉化code::blocks

code::blocks是一个十分好用编辑环境,一个在手,无所不能,为了更好的支持中文,我 列出了汉化的方法: 1下载中文汉化包(见附件): d2school_codeblocks_chinese_locale .7 z 2.解压文件(方法见上篇文章),得到一 个文件夹:zh_CN,里面有汉化包文件. 3.打开终端:在codeblocks的安装文件夹中新 建一个文件夹:locale mkdir /usr/share/codeblocks/locale 4.把解压得到文 件夹zh_CN移动到以上刚