我应该在嵌套块中使用 weakSelf 吗?

Should I use weakSelf in nested blocks?

我正在尝试正确避免使用 Objective C 中的块保留循环,但不确定是否有嵌套块。

如果我这样写一个简单的块:

[self doSomethingWithBlock:^{
    [self doSomethingElse];
}];

编译器捕获并警告我这可能会导致保留循环。我将其更改如下以避免循环:

__weak __typeof(self)weakSelf = self;
[self doSomethingWithBlock:^{
    __strong __typeof(weakSelf)strongSelf = weakSelf;
    [strongSelf doSomethingElse];
}];

当我这样写的时候:

[self doSomethingWithBlock:^(MyObject* object){
    [object doSomethingElseWithBlock:^{
        [self doYetAnotherThing];
    }];
}];

编译器很满意,但我不相信它是安全的。虽然中间有一个中介object,但从概念上看还是和上面一样,只不过现在是一个有3个retains的循环

应该这样吗?

[self doSomethingWithBlock:^(MyObject* object){
    __weak __typeof(self)weakSelf = self;
    [object doSomethingElseWithBlock:^{
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        [strongSelf doYetAnotherThing];
    }];
}];

或者像这样?

__weak __typeof(self)weakSelf = self;
[self doSomethingWithBlock:^(MyObject* object){
    [object doSomethingElseWithBlock:^{
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        [strongSelf doYetAnotherThing];
    }];
}];

查看此 question 的答案。

我会这样做

__weak typeof (self) weakSelf = self;
[self doSomethingWithBlock:^(MyObject* object){
    [object doSomethingElseWithBlock:^{
        [weakSelf doYetAnotherThing];
    }];
}];

Xcode 8 beta 4 为 self 关键字加下划线,并警告可能在块内使用它的保留周期。

根据 Apple Developer Connection 在 Objective-C 中的编程(使用块):

Avoid Strong Reference Cycles when Capturing self If you need to capture self in a block, such as when defining a callback block, it’s important to consider the memory management implications.

Blocks maintain strong references to any captured objects, including self, which means that it’s easy to end up with a strong reference cycle if, for example, an object maintains a copy property for a block that captures self:

@interface XYZBlockKeeper : NSObject
@property (copy) void (^block)(void);
@end
@implementation XYZBlockKeeper
- (void)configureBlock {
    self.block = ^{
        [self doSomething];    // capturing a strong reference to self
                               // creates a strong reference cycle
    };
}
...
@end

The compiler will warn you for a simple example like this, but a more complex example might involve multiple strong references between objects to create the cycle, making it more difficult to diagnose.

To avoid this problem, it’s best practice to capture a weak reference to self, like this:

- (void)configureBlock {
    XYZBlockKeeper * __weak weakSelf = self;
    self.block = ^{
        [weakSelf doSomething];   // capture the weak reference
                                  // to avoid the reference cycle
    }
}

By capturing the weak pointer to self, the block won’t maintain a strong relationship back to the XYZBlockKeeper object. If that object is deallocated before the block is called, the weakSelf pointer will simply be set to nil.

据报道,该站点提供了一种方法,可以在块内使用 self 关键字时使其变弱;它还提供了返回弱自我或 class 以前强大的对象的说明,再次强大:

https://coderwall.com/p/vaj4tg/making-all-self-references-in-blocks-weak-by-default

在这种情况下,您不必担心循环引用。您担心的是实际上不再需要对象 self 的情况,但是在嵌套块中使用 self 会使它不必要地保持活动状态。例如,如果你有一个视图控制器,当视图被屏幕移除时应该消失,但你下载了一张你想在控制器视图中显示的图像。如果图像在视图消失后很长时间才到达,那么您不希望视图控制器再活动。

最好的是

__weak typeof (self) weakSelf = self;

在调用最外层方法之前。然后在每个应该使用 self 的块中,添加

typeof (self) strongSelf = weakSelf;

并在该块中使用 strongSelf。根据情况,您可能想检查 strongSelf 是否在此时不为 nil,但是当它为 nil 时向 strongSelf 发送消息无效,因此如果您所做的只是发送消息并获取或设置属性,那么检查因为没有必要。

如果不这样做会怎样?不同之处在于,如果您在任何地方(或仅在最里面的块中)都使用 self ,那么 self 可能会不必要地保持在最里面的块中。

你不应该仅仅因为你从编译器那里得到警告就弱捕获一些东西;编译器警告只是猜测;它不知道您调用的方法如何进行引用。仅当您了解引用的体系结构并确定存在循环并确定捕获弱引用仍然保留预期行为时,才应执行此操作。您还没有向我们展示 -doSomethingWithBlock: 的代码。如果在该方法内部将块分配给 属性 或 self 的实例变量,它只会创建一个保留循环。它是否这样做?如果不是,那么就没有retain cycle,外块捕获weaklyself也没有意义。

假设外块弱捕获self是对的,外块强捕获self的例子就不成立了。剩下的问题是内部块是否应该强烈捕获 self(或 self 的任何版本,例如 strongSelf,是合适的)。换句话说,你是否会做这样的事情:

__weak __typeof(self) weakSelf = self;
[self doSomethingWithBlock:^(MyObject* object){
    __strong __typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
        [object doSomethingElseWithBlock:^{
            [strongSelf doYetAnotherThing];
        }];
    }
}];

或类似这样的东西:

__weak __typeof(self) weakSelf = self;
[self doSomethingWithBlock:^(MyObject* object){
    __strong __typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
        [object doSomethingElseWithBlock:^{
            __strong __typeof(weakSelf) strongSelf = weakSelf;
            if (strongSelf) {
                [strongSelf doYetAnotherThing];
            }
        }];
    }
}];

同样,要确定的主要问题是如果内部块强捕获self,是否存在循环保留。如果 [object doSomethingElseWithBlock:... 以某种方式将块分配给 属性 或 self 的实例变量,则只会有一个保留周期。但怎么可能呢?该方法是在 object 上调用的,而不是在 self 上调用的。该方法不会以任何方式获得 self。除非有一些复杂的事情发生,否则该方法不会分配给 属性 或 self 的实例变量,因此不太可能创建保留循环。这意味着内部块弱捕获 self 不是防止保留循环所必需的。

但是内部块捕获 self 是弱还是强会影响行为。即,如果内部块捕获 self 弱,则 self 可以在块为 运行 时被释放,在这种情况下 [strongSelf doYetAnotherThing]; 将不会被执行,而如果强烈捕获 self 内部块,它将使 self 保持活动状态并执行 [strongSelf doYetAnotherThing];。所以这取决于 -doYetAnotherThing 做什么。如果它在 self 上执行一些 UI 操作,这是一个 UI 视图或其他东西,那么您是否在不再显示的视图上执行它没有区别。但是,如果它例如向网络发送一些东西或其他东西,那么它是否被执行会产生很大的不同。