如何在执行API时避免在块中捕获自己?
我有一个工作的应用程序,我正在将其转换为Xcode 4.2中的ARC。 其中一个预先检查的警告涉及在一个导致保留周期的块中强烈地捕获self
。 我已经做了一个简单的代码示例来说明问题。 我相信我明白这意味着什么,但我不确定实施这种情况的“正确”或推荐的方式。
- self是类MyAPI的一个实例
- 下面的代码被简化为只显示与我的问题相关的对象和块的交互
- 假设MyAPI从远程源获取数据,MyDataProcessor使用该数据并生成输出
- 处理器configuration有块来传输进度和状态
代码示例:
// code sample self.delegate = aDelegate; self.dataProcessor = [[MyDataProcessor alloc] init]; self.dataProcessor.progress = ^(CGFloat percentComplete) { [self.delegate myAPI:self isProcessingWithProgress:percentComplete]; }; self.dataProcessor.completion = ^{ [self.delegate myAPIDidFinish:self]; self.dataProcessor = nil; }; // start the processor - processing happens asynchronously and the processor is released in the completion block [self.dataProcessor startProcessing];
问题:我在做什么“错误”和/或应如何修改以符合ARC公约?
简短的回答
不要直接访问self
,而应该从不会保留的引用间接访问它。 如果你没有使用自动引用计数(ARC) ,你可以这样做:
__block MyDataProcessor *dp = self; self.progressBlock = ^(CGFloat percentComplete) { [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete]; }
__block
关键字标记可以在块内部修改的variables(我们不这样做),但是当块被保留时(除非使用ARC),它们不会自动保留。 如果你这样做,你必须确保在MyDataProcessor实例被释放之后,没有别的东西会尝试执行这个块。 (鉴于你的代码的结构,这应该不是一个问题。) 了解更多关于__block
。
如果使用ARC , __block
的语义__block
发生变化,并且引用将被保留,在这种情况下,您应该声明__weak
。
长答案
假设你有这样的代码:
self.progressBlock = ^(CGFloat percentComplete) { [self.delegate processingWithProgress:percentComplete]; }
这里的问题是,自我保留了对块的引用; 与此同时,块必须保留对自己的引用,以便获取其委托属性并向委托发送一个方法。 如果应用程序中的所有内容都释放对该对象的引用,则其保留计数不会为零(因为该块指向该对象),并且该块没有任何错误(因为该对象指向它)等等这对对象将会泄漏到堆中,占用内存,但是如果没有debugging器,将永远无法访问。 真的很悲惨
这种情况可以很容易地通过这样做来解决:
id progressDelegate = self.delegate; self.progressBlock = ^(CGFloat percentComplete) { [progressDelegate processingWithProgress:percentComplete]; }
在这段代码中,self保留了block,block保留了delegate,并且没有任何循环(从这里可见;代表可以保留我们的对象,但是现在我们不在)。 此代码不会以相同的方式冒险泄漏,因为在创build块时会捕获委托属性的值,而不是在执行时查找。 副作用是,如果在创build该块后更改委托,该块仍然会将更新消息发送到旧委托。 这是否可能发生取决于您的应用程序。
即使你对这种行为很酷,你仍然不能在你的情况下使用这个技巧:
self.dataProcessor.progress = ^(CGFloat percentComplete) { [self.delegate myAPI:self isProcessingWithProgress:percentComplete]; };
在这里,你直接将self
传递给方法调用中的委托,所以你必须把它放在某个地方。 如果您可以控制块types的定义,最好的办法是将委托作为parameter passing给块。
self.dataProcessor.progress = ^(MyDataProcessor *dp, CGFloat percentComplete) { [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete]; };
此解决scheme避免了保留周期, 并始终调用当前的委托。
如果你不能改变块,你可以处理它 。 保留周期的原因是一个警告,而不是一个错误,是他们不一定会为你的应用程序拼命。 如果MyDataProcessor
在操作完成时能够释放块,则在父级尝试释放块之前,循环将被打破,所有内容都将被正确清理。 如果你能确定这一点,那么正确的做法是使用#pragma
来压制这段代码的警告。 (或使用每个文件的编译器标志,但不要禁用整个项目的警告。)
你也可以使用上面的类似的技巧,声明一个引用弱或不存在,并在块中使用。 例如:
__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]; }
以上三个都会给你一个参考,但不会保留结果,尽pipe它们的performance都有所不同: __weak
会在释放对象时尝试将参考归零; __unsafe_unretained
会给你一个无效的指针; __block
实际上会增加另一个间接级别,并且允许你改变块内引用的值(在这种情况下无关紧要,因为在其他地方没有使用dp
)。
什么是最好的将取决于你能够改变什么代码,什么你不能。 但希望这给了你一些如何进行的想法。
当你肯定周期在未来将被打破的时候,也可以select压制警告:
#pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-retain-cycles" self.progressBlock = ^(CGFloat percentComplete) { [self.delegate processingWithProgress:percentComplete]; } #pragma clang diagnostic pop
这样你就不必用__weak
, self
别名和明确的ivar前缀四处乱跑。
对于一个通用的解决scheme,我有这些在预编译头定义。 避免捕获并通过避免使用id
仍然启用编译器帮助
#define BlockWeakObject(o) __typeof(o) __weak #define BlockWeakSelf BlockWeakObject(self)
然后在代码中你可以这样做:
BlockWeakSelf weakSelf = self; self.dataProcessor.completion = ^{ [weakSelf.delegate myAPIDidFinish:weakSelf]; weakSelf.dataProcessor = nil; };
我相信没有ARC的解决scheme也适用于ARC,使用__block
关键字:
编辑:每个过渡到ARC发行说明 ,用__block
存储声明的对象仍然保留。 使用__weak
(首选)或__unsafe_unretained
(为了向后兼容)。
// 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];
结合几个其他的答案,这是我现在使用的一个types弱自我使用块:
__typeof(self) __weak welf = self;
我将其设置为一个XCode代码片段 ,在方法/函数中使用完成前缀“welf”,在input“we”后命中。
警告=>“在块内部捕捉自我可能会导致保留周期”
当你在一个自己强烈保留的区块内引用自己或它的财产时,而不是上面显示的警告。
所以为了避免它,我们必须让它一周参考
__weak typeof(self) weakSelf = self;
所以不要使用
blockname=^{ self.PROPERTY =something; }
我们应该使用
blockname=^{ weakSelf.PROPERTY =something; }
注意:保留周期通常发生在一些如何两个对象相互引用,其中两个引用count = 1,他们的delloc方法永远不会被调用。
如果您确定您的代码不会创build保留周期,或者稍后会破坏这个循环,那么使警告无效的最简单方法是:
// code sample self.delegate = aDelegate; self.dataProcessor = [[MyDataProcessor alloc] init]; [self dataProcessor].progress = ^(CGFloat percentComplete) { [self.delegate myAPI:self isProcessingWithProgress:percentComplete]; }; [self dataProcessor].completion = ^{ [self.delegate myAPIDidFinish:self]; self.dataProcessor = nil; }; // start the processor - processing happens asynchronously and the processor is released in the completion block [self.dataProcessor startProcessing];
这样做的原因是Xcode的分析考虑到了属性的点访问,因此
xyz = ^{ block that retains x}
被认为具有y的x(在赋值的左侧)和x的y(在右侧)的保留,方法调用不受相同的分析,即使它们是属性访问方法调用即使在这些属性访问方法是由编译器生成的情况下,也相当于点访问
[xy].z = ^{ block that retains x}
只有右侧被视为创build保留(由x的y),并且不产生保留周期警告。