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

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

4

3 回答 3

53

你不需要做两组弱引用。使用块要避免的是保留循环——两个对象在不必要的情况下使彼此保持活动状态。

如果我有一个具有此属性的对象:

@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因此不会保持该块活动。

希望这可以帮助。

于 2013-09-26T03:05:41.377 回答
23

两种结构都很好。这仅取决于您的意图。如果对象 (a) 在外部块开始之后但 (b) 在内部块在主队列上开始之前被释放,您希望发生什么?如果您不希望它在这种情况下保留(我可能猜想这是您的意图,因为您首先要进行此weakSelf练习),然后使用您的最后一个示例,其中您有第二个弱指针。否则,您可以使用其他示例。

话虽如此,但有几点意见:

  1. weakSelf您必须首先使用这种模式并不是一个定论。有些人错误地认为他们必须使用这种weakSelf模式来避免强引用循环(也称为保留循环)。但是这个代码示例并不构成强引用循环。它只是在分派的代码执行时保留对象,这是一个非常不同的考虑。

    事实上,有时你需要/想要那个。有时你不知道。这取决于您要解决的业务问题。当然,您通常不希望它保持对 的强引用self,在这种情况下,该weakSelf模式非常有意义。但情况并非总是如此。

    但我的观点是,你不应该追求这种weakSelf模式(至少在这种dispatch_async情况下)来避免强引用循环。不存在这样的循环。这是一个问题的地方是你有一个块变量(例如一些completionHandler块)。在这种情况下,weakSelf模式至关重要。但不是在这里。

  2. 但是,让我们再考虑一下您不希望self保留的情况。然后有一个问题,您是否希望分派的代码首先继续。如果没有,也许您应该使用具有可取消操作的操作队列而不是 GCD。

    例如,当一些后台网络请求正在运行时,人们经常会为是否要保留视图控制器而苦恼,但并不担心他们是否应该首先取消该后台网络请求,这让我感到惊讶。通常,后者是一个更重要的设计考虑因素(例如,您正在下载的 PDF 或图像比视图控制器占用更多的系统资源(内存和网络带宽))。

  3. 但是让我们假设(a)您确实希望分派的代码继续执行,但是(b)您不想保留self. (这似乎是一种罕见的情况,但这是你所问的,所以让我们继续。)最后一个问题是你是否需要你的strongSelf构造,也是。在您的情况下,您只是调用 的单个方法self,您不需要为这个strongSelf构造而烦恼。仅当您要尊重 ivars 或需要避免竞争条件时,这一点才至关重要。但是,在这个例子中,鉴于发送给nil对象的消息什么都不做,从技术上讲,您通常根本不需要担心这个strongSelf构造。

不要误会我的意思。将一个人的手臂环绕在weakSelf图案以及strongSelf有时伴随它的嵌套图案上是件好事。我只是建议最好了解何时真正需要这些模式。而且我认为 GCD 与可取消的选择NSOperation通常是一个更为关键但经常被忽视的问题。

于 2013-09-26T03:35:43.433 回答
3

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

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

于 2014-07-02T09:41:08.173 回答