iOS块和对self的强引用/弱引用

42

我对iOS中块中self的强引用和弱引用有疑问。我知道在块内正确地引用self的方法是在块外创建一个弱引用,然后在块内对该弱引用创建一个强引用,像这样:

__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^ {
    typeof(self) strongSelf = weakSelf;
    NSLog(@"%@", strongSelf.someProperty);
});

然而,如果您有嵌套的块会发生什么情况?一个引用集是否足够?还是每个块都需要一个新的引用集?例如,以下哪个是正确的?

这个:

__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^ {
    typeof(self) strongSelf = weakSelf;
    NSLog(@"%@", strongSelf.someProperty);
    dispatch_async(dispatch_get_main_queue(), ^ {
        strongSelf.view.frame = CGRectZero;
    });
});

或者这个:

__weak typeof(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^ {
        typeof(self) strongSelf = weakSelf;
        NSLog(@"%@", strongSelf.someProperty);
        __weak typeof(strongSelf) weakSelf1 = strongSelf;
        dispatch_async(dispatch_get_main_queue(), ^ {
            typeof(strongSelf) strongSelf1 = weakSelf1;
            strongSelf1.view.frame = CGRectZero;
        });
    });

非常感谢任何信息或解释!


为什么我们应该在块内创建强引用到弱引用? - BergP
3个回答

52

您不需要创建两个弱引用,使用闭包的目的是避免保留环——即两个对象相互保持不必要的存活。

如果我有一个具备以下属性的对象:

@property (strong) void(^completionBlock)(void);

我有这个方法:

- (void)doSomething
{
    self.completionBlock = ^{
        [self cleanUp];
    };

    [self doLongRunningTask];
}

当我将代码块存储在completionBlock属性中时,代码块将保持活动状态。但由于代码块内部引用了self,因此代码块会一直保持self的活动状态,直到它消失—但这不会发生,因为它们彼此引用。

在这个方法中:

- (void)doSomething
{
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        [self cleanUp];
    }];

    [self doLongRunningTask];
}

你不需要对self进行弱引用。块将保留self的活性,因为它从内部引用self,但由于我们所做的一切都是将块交给[NSOperationQueue mainQueue],所以self并没有保持块的活性。

希望这能帮到你。


23

这两种构造都没问题,只取决于你的意图。如果对象在外部块开始执行后但在内部块在主队列开始之前被释放,你希望发生什么?如果你不想在这种情况下保留它(考虑到你首先经历这个 weakSelf 练习,我猜测这是你的意图),那么使用你的最终示例,其中有第二个弱指针。否则,可以使用另一个示例。

话虽如此,有一些观察点:

  1. 并不是必须在首次使用 weakSelf 模式时使用该模式。有些人错误地认为他们必须使用这个 weakSelf 模式来避免强引用循环(也称为保留周期)。但是,此代码示例并没有构成强引用循环。它只是在调度代码执行时保留对象,这是一个非常不同的考虑因素。

    实际上,有时您需要/想要这个。有时候不需要。这取决于您所解决的业务问题。绝对地,您经常不希望它保留对 self 的强引用,在这种情况下,weakSelf 模式完全是有道理的。但这并不总是如此。

    但我的观点是,你不应该追求这个 weakSelf 模式(至少在这个 dispatch_async 场景中)来避免强引用循环。没有这样的周期存在。当问题出现在块变量(例如某些 completionHandler 块)时,这就成为一个问题。在那种情况下,weakSelf 模式非常关键。但在这里不是。

  2. 但让我们考虑一下您不想保留 self 的情况。然后就有一个问题,是否要首先继续调度代码。如果不想,请使用具有可取消操作的操作队列而不是 GCD。

例如,我很惊讶人们经常在纠结是否在后台网络请求运行时保留视图控制器,但不担心是否应该首先取消这个后台网络请求。后者通常是一个更重要的设计考虑因素(例如,你正在下载的 PDF 或图片占用比视图控制器更多的系统资源(内存和网络带宽))。

  • 但假设你确实想让调度的代码继续执行,但又不想保留self。(这似乎是一个罕见的情况,但这是你提出的问题,所以让我们来追寻一下。)最后一个问题是你是否需要strongSelf构造。在你只是调用self的单个方法时,不需要使用strongSelf构造。这只有在你要解引用 ivar 或需要避免竞争条件时才是关键。但是,在这个例子中,由于向nil对象发送的消息什么也不做,你通常根本不需要担心这个strongSelf构造。

  • 别误会我的意思,了解weakSelf模式以及有时伴随着它的嵌套strongSelf模式是很好的。我只是建议要了解何时真正需要这些模式。而我认为选择GCD还是可取消的NSOperation通常是一个更重要但经常被忽视的问题。


    3

    块是在堆栈上创建和存储的。因此,当创建块的方法返回时,块将被销毁。

    如果一个块变成一个实例变量,ARC会将该块从堆栈复制到堆中。您可以使用复制消息显式地复制一个块。现在,您的块是基于堆的块而不是基于栈的块。您需要处理一些内存管理问题。块本身将保持对其引用的任何对象的强引用。在块外部声明__weak指针,然后在块内引用此指针以避免保留循环。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接