How do I avoid capturing self in blocks when implementing an API?
我有一个正在运行的应用程序,我正在把它转换成Xcode4.2中的ARC。其中一个预检查警告包括在导致保留周期的块中强烈捕获
- self是类myapi的一个实例
- 下面的代码被简化为只显示与我的问题相关的对象和块之间的交互。
- 假设myapi从远程源获取数据,mydataprocessor处理该数据并生成一个输出
- 处理器配置了块来通信进度和状态
代码示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // 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惯例?
简短回答
您不应该直接访问
1 2 3 4 | __block MyDataProcessor *dp = self; self.progressBlock = ^(CGFloat percentComplete) { [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete]; } |
如果您使用ARC,那么将保留
假设您有这样的代码:
1 2 3 | self.progressBlock = ^(CGFloat percentComplete) { [self.delegate processingWithProgress:percentComplete]; } |
这里的问题是self保留了对块的引用;同时块必须保留对self的引用,以便获取其委托属性并向委托发送方法。如果应用程序中的其他所有内容都释放了对该对象的引用,则其保留计数不会为零(因为块指向它),并且块没有做任何错误(因为对象指向它),因此这对对象将泄漏到堆中,占用内存,但在没有调试器的情况下永远无法访问。悲剧,真的。
这样做可以很容易地解决这个问题:
1 2 3 4 | id progressDelegate = self.delegate; self.progressBlock = ^(CGFloat percentComplete) { [progressDelegate processingWithProgress:percentComplete]; } |
在这段代码中,self保留了块,块保留了委托,并且没有循环(从这里可以看到;委托可能保留了我们的对象,但现在已经超出了我们的控制)。这段代码不会以同样的方式冒泄漏的风险,因为在创建块时会捕获委托属性的值,而不是在执行块时进行查找。一个副作用是,如果在创建此块后更改委托,则该块仍将向旧委托发送更新消息。这是否可能发生取决于您的应用程序。
即使你对这种行为很冷静,你仍然不能在你的案例中使用这种技巧:
1 2 3 | self.dataProcessor.progress = ^(CGFloat percentComplete) { [self.delegate myAPI:self isProcessingWithProgress:percentComplete]; }; |
这里,您在方法调用中将
1 2 3 | self.dataProcessor.progress = ^(MyDataProcessor *dp, CGFloat percentComplete) { [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete]; }; |
此解决方案可避免保留周期,并始终调用当前委托。
如果你不能改变这个街区,你可以处理它。保留周期是一个警告,而不是一个错误,原因是它们不一定为您的应用程序拼写厄运。如果
您还可以使用上面类似的技巧,声明一个弱引用或不包含引用,并在块中使用这个技巧。例如:
1 2 3 4 5 6 | __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]; } |
以上三种方法都会在不保留结果的情况下给出一个引用,尽管它们的行为都有点不同:当对象被释放时,
什么是最好的取决于你能改变什么代码,什么是不能改变的。但希望这能给你一些关于如何进行的想法。
还有一个选项可以在您确定循环将在未来中断时抑制警告:
1 2 3 4 5 6 7 8 | #pragma clang diagnostic push #pragma clang diagnostic ignored"-Warc-retain-cycles" self.progressBlock = ^(CGFloat percentComplete) { [self.delegate processingWithProgress:percentComplete]; } #pragma clang diagnostic pop |
这样,您就不必对
对于一个常见的解决方案,我在预编译头中有这些定义。避免捕获并仍然通过避免使用
1 2 | #define BlockWeakObject(o) __typeof(o) __weak #define BlockWeakSelf BlockWeakObject(self) |
然后在代码中,您可以执行以下操作:
1 2 3 4 5 | BlockWeakSelf weakSelf = self; self.dataProcessor.completion = ^{ [weakSelf.delegate myAPIDidFinish:weakSelf]; weakSelf.dataProcessor = nil; }; |
结合其他几个答案,这就是我现在使用的类型弱的自我在块中使用的方法:
1 | __typeof(self) __weak welf = self; |
我将其设置为Xcode代码片段,在方法/函数中的完整前缀为"welf",只在键入"we"后点击。
我相信没有ARC的解决方案也适用于ARC,使用
编辑:根据转换到ARC发行说明,使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // 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]; |
警告=>"在块内捕获自我可能导致保留周期"
当您将自身或其属性引用到自身强烈保留的块中时,该块将显示上述警告。
所以为了避免这种情况,我们必须把它作为一周的参考。
1 | __weak typeof(self) weakSelf = self; |
所以不用
1 2 3 | blockname=^{ self.PROPERTY =something; } |
我们应该使用
1 2 3 | blockname=^{ weakSelf.PROPERTY =something; } |
注意:当两个对象彼此引用时,引用计数都为1,并且永远不会调用它们的delloc方法时,通常会发生retain循环。
新的方法是使用@weakify和@strongify marco
1 2 3 4 5 | @weakify(self); [self methodThatTakesABlock:^ { @strongify(self); [self doSomething]; }]; |
更多关于@weakify@strongify marco的信息
如果您确定您的代码不会创建一个保留周期,或者稍后会中断该周期,那么消除警告的最简单方法是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // 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的分析考虑了属性的点访问,因此
1 | x.y.z = ^{ block that retains x} |
被视为具有x of y(在赋值的左侧)和y of x(在右侧)的保留,方法调用不接受相同的分析,即使它们是等同于点访问的属性访问方法调用,即使这些属性访问方法是编译器生成的,因此
1 | [x y].z = ^{ block that retains x} |
只有右侧被视为创建保留(Y为X),并且不会生成保留周期警告。