ios 实现 API 时如何避免在块中捕获 self ?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7853915/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 21:59:52  来源:igfitidea点击:

How do I avoid capturing self in blocks when implementing an API?

objective-ciosobjective-c-blocksautomatic-ref-counting

提问by XJones

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

我有一个可用的应用程序,我正在 Xcode 4.2 中将其转换为 ARC。预检查警告之一涉及self在导致保留循环的块中强烈捕获。我制作了一个简单的代码示例来说明这个问题。我相信我理解这意味着什么,但我不确定实现此类场景的“正确”或推荐方法。

  • 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
  • self 是类 MyAPI 的一个实例
  • 下面的代码被简化为仅显示与我的问题相关的对象和块的交互
  • 假设 MyAPI 从远程源获取数据,MyDataProcessor 处理该数据并产生输出
  • 处理器配置有块来传达进度和状态

code sample:

代码示例:

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

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

问题:我在做什么“错误”和/或应该如何修改以符合 ARC 约定?

回答by benzado

Short answer

简答

Instead of accessing selfdirectly, 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:

self您应该从不会保留的引用中间接访问它,而不是直接访问它。如果您不使用自动引用计数 (ARC),您可以这样做:

__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
    [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}

The __blockkeyword 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.

__block可以在块里面修改关键字标记变量(我们不这样做),而且他们没有当块被保留自动保留(除非您使用ARC)。如果您这样做,您必须确保在释放 MyDataProcessor 实例后没有其他任何东西会尝试执行该块。(鉴于您的代码结构,这应该不是问题。)阅读更多关于__block.

If you are using ARC, the semantics of __blockchanges and the reference will be retained, in which case you should declare it __weakinstead.

如果您使用 ARC__block更改的语义和引用将被保留,在这种情况下,您应该__weak改为声明它。

Long answer

长答案

Let's say you had code like this:

假设你有这样的代码:

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.

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

That case could be easily fixed by doing this instead:

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

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 保留块,块保留委托,并且没有循环(从这里可见;委托可能保留我们的对象,但现在我们无法控制)。这段代码不会以同样的方式冒泄漏的风险,因为委托属性的值是在创建块时捕获的,而不是在执行时查找。一个副作用是,如果在创建此块后更改委托,该块仍会向旧委托发送更新消息。这是否可能发生取决于您的应用程序。

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

Here you are passing selfdirectly 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在方法调用中直接传递给委托,因此您必须将它放在某个地方。如果您可以控制块类型的定义,最好的办法是将委托作为参数传递到块中:

self.dataProcessor.progress = ^(MyDataProcessor *dp, CGFloat percentComplete) {
    [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
};

This solution avoids the retain cycle andalways calls the current delegate.

此解决方案避免了保留循环始终调用当前委托。

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 MyDataProcessoris 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 #pragmato 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.)

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

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

All three of the above will give you a reference without retaining the result, though they all behave a little bit differently: __weakwill try to zero the reference when the object is released; __unsafe_unretainedwill leave you with an invalid pointer; __blockwill 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 dpisn't used anywhere else).

以上三个都会给你一个引用而不保留结果,尽管它们的行为都有点不同:__weak当对象被释放时会尝试将引用归零;__unsafe_unretained会给你一个无效的指针;__block实际上将添加另一个间接级别,并允许您从块内更改引用的值(在这种情况下无关紧要,因为dp没有在其他任何地方使用)。

What's bestwill 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.

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

回答by zoul

There's also the option to suppress the warning when you are positive that the cycle will get broken in the future:

当您确定循环将来会被打破时,还可以选择取消警告:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"

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

#pragma clang diagnostic pop

That way you don't have to monkey around with __weak, selfaliasing and explicit ivar prefixing.

这样你就不必乱用__weakself别名和显式 ivar 前缀。

回答by Damien Pontifex

For a common solution, I have these define in the precompile header. Avoids capturing and still enables compiler help by avoiding to use id

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

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

Then in code you can do:

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

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

回答by Tony

I believe the solution without ARC also works with ARC, using the __blockkeyword:

我相信没有 ARC 的解决方案也适用于 ARC,使用__block关键字:

EDIT: Per the Transitioning to ARC Release Notes, an object declared with __blockstorage is still retained. Use __weak(preferred) or __unsafe_unretained(for backwards compatibility).

编辑:根据过渡到 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];

回答by Kendall Helmstetter Gelner

Combining a few other answers, this is what I use now for a typed weak self to use in blocks:

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

__typeof(self) __weak welf = self;

I set that as an XCode Code Snippetwith a completion prefix of "welf" in methods/functions, which hits after typing only "we".

我将它设置为一个XCode 代码片段,在方法/函数中的完成前缀为“welf”,仅在键入“we”后命中。

回答by Anurag Bhakuni

warning => "capturing self inside the block is likely to lead a retain cycle"

警告 => “在块内捕获 self 可能会导致一个保留循环”

when you referring self or its property inside a block which is strongly retain by self than it shows above warning.

当您在自我强烈保留的块中引用 self 或其属性时,它比上面警告显示的要多。

so for avoiding it we have to make it a week ref

所以为了避免它,我们必须让它一周参考

__weak typeof(self) weakSelf = self;

so instead of using

所以而不是使用

blockname=^{
    self.PROPERTY =something;
}

we should use

我们应该使用

blockname=^{
    weakSelf.PROPERTY =something;
}

note:retain cycle is usually occurs when some how two object referring to each other by which both has reference count =1 and their delloc method is never get called.

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

回答by BananZ

The new way to do this is by using @weakify and @strongify marco

执行此操作的新方法是使用 @weakify 和 @strongify marco

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

More Info about @Weakify @Strongify Marco

更多关于@Weakify @Strongify Marco 的信息

回答by Ben Artin

If you are sure that your code will not create a retain cycle, or that the cycle will be broken later, then the simplest way to silence the warning is:

如果您确定您的代码不会创建保留循环,或者循环稍后会被破坏,那么消除警告的最简单方法是:

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

The reason that this works is that while dot-access of properties is taken into account by Xcode's analysis, and therefore

这样做的原因是,虽然 Xcode 的分析考虑了属性的点访问,因此

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

is seen as having a retain by x of y (on the left side of the assignment) and by y of x (on the right side), method calls are not subject to the same analysis, even when they are property-access method calls that are equivalent to dot-access, even when those property access methods are compiler-generated, so in

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

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

only the right side is seen as creating a retain (by y of x), and no retain cycle warning is generated.

只有右侧被视为创建保留(由 x 的 y),并且不会生成保留循环警告。