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
 
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

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
 
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

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
 
   
   
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

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: 572064792 | Nodejs:329118122 做人要厚道,转载请注明出处!

















本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sunshine-anycall/p/3514235.html ,如需转载请自行联系原作者

相关文章
|
17天前
|
SQL 安全 API
api接口是什么意思,api接口该如何防护呢?
api接口是什么意思,api接口该如何防护呢?
|
18天前
|
机器学习/深度学习 搜索推荐 API
商品信息全景图:API接口在聚合商品数据中的应用
在电子商务的世界中,API接口是连接不同服务和数据的桥梁。特别是在商品信息的聚合上,API接口扮演了至关重要的角色,它允许开发者从多个来源收集、整合并展示商品信息,从而为消费者提供全面且一致的购物体验。本文将深入探讨API接口在聚合商品数据中的应用,并通过具体的代码示例来揭示其工作原理。
|
19天前
|
存储 开发框架 小程序
社区每周丨小程序 CLI 1.8.10 版本上线及基础API新增接口(7.3-7.7)
社区每周丨小程序 CLI 1.8.10 版本上线及基础API新增接口(7.3-7.7)
30 0
|
19天前
|
小程序 API 开发者
【产品上新】接口不报错=API成功接入?不妨“验”一下
【产品上新】接口不报错=API成功接入?不妨“验”一下
19 0
|
20天前
|
小程序 安全 API
社区每周丨小程序基础API新增获取设备、系统等多个接口
社区每周丨小程序基础API新增获取设备、系统等多个接口
44 0
|
22天前
|
搜索推荐 安全 API
API接口的艺术:如何巧妙获取商品数据
在数字时代,API接口已经成为连接不同软件系统、共享数据的桥梁。尤其在电商领域,商品数据的实时获取和处理对于提供个性化服务、优化用户体验至关重要。本文将深入探讨API接口的艺术,以及如何通过它们高效地获取和管理商品数据。
|
22天前
|
数据采集 传感器 人工智能
大数据关键技术之电商API接口接入数据采集发展趋势
本文从数据采集场景、数据采集系统、数据采集技术方面阐述数据采集的发展趋势。 01 数据采集场景的发展趋势 作为大数据和人工智能工程的源头,数据采集的场景伴随着应用场景的发展而变化,以下是数据采集场景的发展趋势。
|
1天前
|
数据采集 数据挖掘 API
主流电商平台数据采集API接口|【Python爬虫+数据分析】采集电商平台数据信息采集
随着电商平台的兴起,越来越多的人开始在网上购物。而对于电商平台来说,商品信息、价格、评论等数据是非常重要的。因此,抓取电商平台的商品信息、价格、评论等数据成为了一项非常有价值的工作。本文将介绍如何使用Python编写爬虫程序,抓取电商平台的商品信息、价格、评论等数据。 当然,如果是电商企业,跨境电商企业,ERP系统搭建,我们经常需要采集的平台多,数据量大,要求数据稳定供应,有并发需求,那就需要通过接入电商API数据采集接口,封装好的数据采集接口更方便稳定高效数据采集。
|
2天前
|
数据采集 数据挖掘 API
如何有效利用API接口进行数据采集
在当今数字化的世界中,API(应用程序编程接口)作为数据交换的桥梁,对于电商企业来说尤为重要。它们允许企业从丰富的数据源中提取必要的信息,为商业决策提供数据支持。本文将围绕如何高效地利用API进行数据采集展开讨论,并提供一些实用的代码示例。
|
3天前
|
JSON 文字识别 API
文字识别OCR服务通常提供了一种API接口
文字识别OCR服务通常提供了一种API接口
30 6