在实现API时,如何避免在块中捕获self? [英] How do I avoid capturing self in blocks when implementing an API?

查看:102
本文介绍了在实现API时,如何避免在块中捕获self?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个正在运行的应用程序,我正在努力将其转换为Xcode 4.2中的ARC。其中一个预检警告涉及在导致保留周期的块中强烈捕获 self 。我已经制作了一个简单的代码示例来说明问题。我相信我理解这意味着什么,但我不确定实现这种情况的正确或推荐方式。

I have a working app and I'm working on converting it to ARC in Xcode 4.2. One of the pre-check warnings involves capturing self strongly in a block leading to a retain cycle. I've made a simple code sample to illustrate the issue. I believe I understand what this means but I'm not sure the "correct" or recommended way to implement this type of scenario.


  • self是MyAPI类的实例

  • 下面的代码被简化为仅显示与我的问题相关的对象和块的交互

  • 假设MyAPI从远程源获取数据,MyDataProcessor处理该数据并生成输出

  • 处理器配置有块来传达进度和进度。州

  • self is an instance of class MyAPI
  • the code below is simplified to show only the interactions with the objects and blocks relevant to my question
  • assume that MyAPI gets data from a remote source and MyDataProcessor works on that data and produces an output
  • the processor is configured with blocks to communicate progress & state

代码示例:

// 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约定?

Question: what am I doing "wrong" and/or how should this be modified to conform to ARC conventions?

推荐答案

简短回答



而不是访问 self 直接,您应该从不会保留的引用间接访问它。 如果您没有使用自动参考计数(ARC),则可以执行以下操作:

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

__ block 关键字标记可以在块内修改的变量(我们没有这样做)但是当保留块时它们不会自动保留(除非您使用ARC)。如果执行此操作,则必须确保在释放MyDataProcessor实例后不会尝试执行该块。 (鉴于您的代码结构,这应该不是问题。)了解更多关于 __ block

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.

如果您使用ARC ,则语义为 __ block 更改并保留引用,在这种情况下,您应该声明 __ weak

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.

假设你有这样的代码:

self.progressBlock = ^(CGFloat percentComplete) {
    [self.delegate processingWithProgress:percentComplete];
}

这里的问题是self保留了对块的引用;同时,块必须保留对self的引用,以便获取其委托属性并向委托发送方法。如果您的应用程序中的其他所有内容都释放其对此对象的引用,则其保留计数将不为零(因为该块指向它)并且该块没有做任何错误(因为该对象指向它),所以这对对象将泄漏到堆中,占用内存但在没有调试器的情况下永远无法访问。悲惨,真的。

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

在此代码中,self保留块,块保留委托,并且没有循环(从这里可以看到;代表可以保留我们的对象,但现在不在我们手中)。此代码不会以相同的方式冒泄漏的风险,因为在创建块时捕获委托属性的值,而不是在执行时查找。副作用是,如果在创建此块后更改委托,则块仍将向旧委托发送更新消息。是否可能发生这种情况取决于你的申请。

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

这里你直接将 self 传递给在方法调用中委托,所以你必须在那里得到它。如果您可以控制块类型的定义,最好的方法是将委托作为参数传递给块:

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.

如果你无法更改块,你可以处理它。保留周期是警告而非错误的原因是它们不一定会为您的应用程序带来厄运。如果 MyDataProcessor 能够在操作完成时释放块,在其父级尝试释放它之前,循环将被破坏并且所有内容都将被正确清理。如果您可以确定这一点,那么正确的做法是使用 #pragma 来禁止该代码块的警告。 (或者使用每个文件的编译器标志。但是不要禁用整个项目的警告。)

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

上述所有三项都会在不保留结果的情况下为您提供参考,它们的行为略有不同: __ weak 将在释放对象时尝试将引用置零; __ unsafe_unretained 将为您留下无效指针; __ block 实际上会添加另一个间接级别,并允许您更改块内的引用值(在这种情况下无关,因为 dp 在其他任何地方都没有使用。)

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.

这篇关于在实现API时,如何避免在块中捕获self?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆