我有一个工作的应用程序,我正在把它转换成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实例释放之后没有其他程序尝试执行该块。(考虑到代码的结构,这应该不是问题。)阅读更多关于__block的信息。

如果你使用ARC, __block的语义会改变,引用会被保留,在这种情况下你应该声明它为__weak。

长回答

假设你有这样的代码:

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.

这种情况可以通过这样做来轻松解决:

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.

即使你对这种行为很冷静,你仍然不能在你的情况下使用这个技巧:

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];
};

这个解决方案避免了保留周期,并且总是调用当前委托。

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

您还可以考虑使用上面类似的技巧,将引用声明为弱引用或未保留引用并在块中使用。例如:

__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没有在其他任何地方使用)。

什么是最好的取决于哪些代码可以更改,哪些代码不能更改。但希望这能给你一些如何进行的想法。


我相信没有ARC的解决方案也适用于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];

当你确信这个循环在未来会被打破时,你也可以选择抑制警告:

#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 aliasing和显式的ivar前缀上瞎折腾了。


对于一个常见的解决方案,我在预编译头中定义了这些。避免捕获,并通过避免使用id启用编译器帮助

#define BlockWeakObject(o) __typeof(o) __weak
#define BlockWeakSelf BlockWeakObject(self)

然后在代码中你可以这样做:

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

如果你确定你的代码不会创建一个保留循环,或者这个循环稍后会被打破,那么最简单的方法是:

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

被视为保留x的y(在赋值的左边)和y的x(在右边),方法调用不受相同的分析,即使当它们是属性访问方法调用,等价于点访问,即使当这些属性访问方法是编译器生成的,那么在

[x y].z = ^{ block that retains x}

只有右侧被视为创建保留(通过y (x)),并且不会生成保留周期警告。


结合其他一些答案,这是我现在在块中使用的类型化弱自我:

__typeof(self) __weak welf = self;

我将其设置为在方法/函数中带有“welf”完成前缀的XCode代码片段,在只输入“we”后命中。


警告=> "在块内捕获自我可能导致保留循环"

当你在一个被self强保留的块中引用self或它的属性时,它会显示上述警告。

为了避免这种情况,我们必须将其设为一周参考

__weak typeof(self) weakSelf = self;

所以不用

blockname=^{
    self.PROPERTY =something;
}

我们应该使用

blockname=^{
    weakSelf.PROPERTY =something;
}

注意:retain循环通常发生在两个对象相互引用时,它们的引用计数都为1,并且它们的delloc方法永远不会被调用。


新的方法是使用@weakify和@ stronggify marco

@weakify(self);
[self methodThatTakesABlock:^ {
    @strongify(self);
    [self doSomething];
}];

更多关于@Weakify @ stronggify马可的信息