我有一个工作应用程序,我正在将其转换为 Xcode 4.2 中的 ARC。其中一个预检查警告涉及在导致保留周期的块中强烈捕获 self
。我制作了一个简单的代码示例来说明这个问题。我相信我理解这意味着什么,但我不确定实现这种场景的“正确”或推荐方式。
self 是 MyAPI 类的一个实例
下面的代码经过简化,仅显示与我的问题相关的对象和块的交互
假设 MyAPI 从远程源获取数据,并且 MyDataProcessor 处理该数据并产生输出
处理器配置有块来传达进度和状态
代码示例:
// 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
关键字标记可以在块内修改的变量(我们不这样做),但在保留块时它们不会自动保留(除非您使用 ARC)。如果您这样做,您必须确保在 MyDataProcessor 实例被释放后没有其他东西会尝试执行该块。 (鉴于您的代码结构,这应该不是问题。)Read more about __block
。
如果您使用的是 ARC,__block
的语义会发生变化,并且引用将被保留,在这种情况下,您应该将其声明为 __weak
。
长答案
假设你有这样的代码:
self.progressBlock = ^(CGFloat percentComplete) {
[self.delegate processingWithProgress:percentComplete];
}
这里的问题是 self 保留了对块的引用;同时,该块必须保留对 self 的引用,以便获取其委托属性并向委托发送方法。如果您的应用程序中的其他所有内容都释放了对此对象的引用,则其保留计数不会为零(因为块指向它)并且块没有做错任何事情(因为对象指向它)等等这对对象将泄漏到堆中,占用内存,但如果没有调试器则永远无法访问。悲剧,真的。
这种情况可以很容易地通过这样做来解决:
id progressDelegate = self.delegate;
self.progressBlock = ^(CGFloat percentComplete) {
[progressDelegate processingWithProgress:percentComplete];
}
在这段代码中,self 保留了块,块保留了委托,并且没有循环(从这里可见;委托可能保留我们的对象,但现在我们无法控制)。这段代码不会以同样的方式发生泄漏,因为委托属性的值是在创建块时捕获的,而不是在执行时查找。副作用是,如果您在创建此块后更改委托,该块仍会向旧委托发送更新消息。这是否可能发生取决于您的应用程序。
即使您对这种行为很酷,您仍然不能在您的情况下使用该技巧:
self.dataProcessor.progress = ^(CGFloat percentComplete) {
[self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};
在这里,您将 self
直接传递给方法调用中的委托,因此您必须在某个地方获取它。如果您可以控制块类型的定义,最好的办法是将委托作为参数传递到块中:
self.dataProcessor.progress = ^(MyDataProcessor *dp, CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
};
此解决方案避免了保留周期并始终调用当前委托。
如果你不能改变这个块,你可以处理它。保留周期是警告而不是错误的原因是它们不一定会为您的应用程序带来厄运。如果 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];
}
以上三个都将在不保留结果的情况下为您提供引用,尽管它们的行为都有点不同:__weak
将在释放对象时尝试将引用归零; __unsafe_unretained
会给你一个无效的指针; __block
实际上会添加另一层间接性,并允许您从块内更改引用的值(在这种情况下不相关,因为 dp
未在其他任何地方使用)。
什么是最好的将取决于您可以更改哪些代码以及您不能更改哪些代码。但希望这能给你一些关于如何进行的想法。
当您确定循环将来会被打破时,还可以选择抑制警告:
#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 前缀。
__weak id weakSelf = self;
具有与抑制警告完全不同的行为。问题以“......如果您确定保留周期将被破坏”开头
[array addObject:weakObject];
如果弱对象已被释放,这会导致崩溃。显然,这不是保留周期的首选。您必须了解您的块实际上是否存在足够长的时间以保证弱化,以及您是否希望块中的动作依赖于弱对象是否仍然有效。
对于一个常见的解决方案,我在预编译头文件中定义了这些。通过避免使用 id
来避免捕获并仍然启用编译器帮助
#define BlockWeakObject(o) __typeof(o) __weak
#define BlockWeakSelf BlockWeakObject(self)
然后在代码中你可以这样做:
BlockWeakSelf weakSelf = self;
self.dataProcessor.completion = ^{
[weakSelf.delegate myAPIDidFinish:weakSelf];
weakSelf.dataProcessor = nil;
};
self
@weakify(self); id 块 = ^{ @strongify(self); [self.delegate myAPIDidFinish:self]; };
我相信没有 ARC 的解决方案也适用于 ARC,使用 __block
关键字:
编辑:根据 Transitioning to ARC Release Notes,仍保留使用 __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];
__block
关键字避免保留它的引用。谢谢!我更新了我的整体答案。 :-)
结合其他一些答案,这就是我现在使用的类型弱自我在块中使用:
__typeof(self) __weak welf = self;
我将其设置为 XCode Code Snippet,在方法/函数中具有“welf”的完成前缀,仅在输入“we”后才会触发。
警告=>“在块内捕获自我可能会导致保留周期”
当您在一个块内引用 self 或其属性时,该块被 self 强烈保留,而不是上面的警告。
所以为了避免它,我们必须让它一周参考
__weak typeof(self) weakSelf = self;
所以而不是使用
blockname=^{
self.PROPERTY =something;
}
我们应该使用
blockname=^{
weakSelf.PROPERTY =something;
}
注意:保留循环通常发生在两个对象如何相互引用时,两个对象的引用计数=1,并且它们的 delloc 方法永远不会被调用。
执行此操作的新方法是使用 @weakify 和 @strongify marco
@weakify(self);
[self methodThatTakesABlock:^ {
@strongify(self);
[self doSomething];
}];
More Info about @Weakify @Strongify Marco
如果您确定您的代码不会创建保留循环,或者该循环稍后会被破坏,那么消除警告的最简单方法是:
// 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 的分析考虑了属性的点访问,因此
x.y.z = ^{ block that retains x}
被视为具有 y 的 x (在赋值的左侧)和 x 的 y (在右侧)的保留,方法调用不受相同的分析,即使它们是属性访问方法调用相当于点访问,即使这些属性访问方法是编译器生成的,所以在
[x y].z = ^{ block that retains x}
只有右侧被视为创建保留(按 x 的 y),并且不会生成保留周期警告。
dp
将被释放(例如,如果它是一个视图控制器并且它被弹出),那么行[dp.delegate ...
将导致 EXC_BADACCESS?strong
还是weak
?@weakify(..)
和@strongify(...)
,它允许您以非保留方式在块中使用self
。