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

查看:26
本文介绍了实现 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 处理该数据并产生输出
  • 处理器配置有块来传达进度和;状态

代码示例:

// 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天全站免登陆