【问题标题】:Can using __weak attribute to pass parameter to blocks lead to memory leaks?使用 __weak 属性将参数传递给块会导致内存泄漏吗?
【发布时间】:2012-05-29 05:53:57
【问题描述】:

在我启用了 iOS ARC 的代码中,我需要将“self”和其他对象传递给一个块。更具体地说,我需要与 self 和 ASIHTTPRequestcompletionBlock 内的 ASIHTTPRequest 对象进行交互。

_operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(parseServerReply) object:nil];
_request = [ASIHTTPRequest requestWithURL:@"server.address"];

// ...

[_request setCompletionBlock:^{
    [self setResponseString:_request.responseString];
    [[MyAppDelegate getQueue] addOperation:_operation];
}];

为了避免以下警告:Capturing "self" strongly in this block will likely lead to a retain cycle。我已经修改了我的代码以添加__weak 属性,这些对象在这篇文章之后的块中使用:Fix warning "Capturing [an object] strongly in this block is likely to lead to a retain cycle" in ARC-enabled code

生成的代码是:

_operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(parseServerReply) object:nil];
_request = [ASIHTTPRequest requestWithURL:@"server.address"];

// ...
__weak NSOperation * operation = _operation;
__weak ASIHTTPRequest * request = _request;
__weak typeof(self) * self_ = self;

[request setCompletionBlock:^{
    [self_ setResponseString:request.responseString];
    [[MyAppDelegate getQueue] addOperation:operation];
}];

我想知道这是否仍会导致保留周期和内存泄漏。如果是这样,有没有办法避免泄漏?

【问题讨论】:

    标签: iphone ios ios5 objective-c-blocks


    【解决方案1】:

    你不需要让所有东西都变弱,也不想一直引用块中的弱对象(特别是如果块可以在不同的线程中执行)。

    这样想。使用 ARC 时,对象被引用计数。在引用计数变为零之前,不会对对象调用 dealloc。因此,只要周围有一个引用,对象就会保持活动状态。

    但是,考虑两个相互具有强引用的对象。在对方释放其引用之前,两者都不会释放。

    当您创建一个块时,它会捕获其环境,这意味着它将创建对该块范围内使用的任何对象的强引用。

    考虑到这一点...

    id object = getMeSomeObject();
    // <object> is implicitly __strong, and now has a reference.
    

    在释放所有强引用之前,该对象不会被释放。如果在块中使用它,块会自动创建自己的强引用,以确保对象与块一样长。

    __weak 引用是一种间接级别,它使您可以在对象存在时访问对象。基本上,如果您将一个对象分配给一个 __weak 指针,那么只要该对象还活着,该指针就可以保证为您提供相同的对象。一旦对象启动它自己的 dealloc(),它会找到所有的 __weak 指针并将它们设置为 nil。

    因此,您的 _weak 指针将始终处于两种状态之一。只要对象存在,它就指向一个有效的对象,或者当对象具有 dealloc 时它是 nil。您应该永远不要通过弱指针访问对象,因为该对象可能会在您背后解除分配,给您留下一个坏指针。

    所以,你想要做的是在堆栈上创建一个 __strong 引用,这样对象就可以在你想要的时候保持活动状态。

    你的情况……

    [_request setCompletionBlock:^{
        [self setResponseString:_request.responseString];
        [[MyAppDelegate getQueue] addOperation:_operation];
    }];
    

    这个块显然包含对self 的强引用。你可能不希望那样。让我们尝试修复它...

    // weakSelf will be "magically" set to nil if <self> deallocs
    __weak SelfType *weakSelf = self;
    [_request setCompletionBlock:^{
        // If <self> is alive right now, I want to keep it alive while I use it
        // so I need to create a strong reference
        SelfType *strongSelf = weakSelf;
        if (strongSelf) {
            // Ah... <self> is still alive...
            [strongSelf setResponseString:_request.responseString];
            [[MyAppDelegate getQueue] addOperation:_operation];
        } else {
            // Bummer.  <self> dealloc before we could run this code.
        }
    }];
    

    嘿,我们现在有一个弱self,但是......你仍然会遇到同样的问题。为什么?因为 _request 和 _operation 是实例变量。如果您访问块内的实例变量,它会隐式创建对self 的强引用。

    再试一次......

    // weakSelf will be "magically" set to nil if <self> deallocs
    __weak SelfType *weakSelf = self;
    [_request setCompletionBlock:^{
        // If <self> is alive right now, I want to keep it alive while I use it
        // so I need to create a strong reference
        SelfType *strongSelf = weakSelf;
        if (strongSelf) {
            // Ah... <self> is still alive...
            [strongSelf setResponseString:strongSelf->_request.responseString];
            [[MyAppDelegate getQueue] addOperation:strongSelf->_operation];
        } else {
            // Bummer.  <self> dealloc before we could run this code.
        }
    }];
    

    现在,您可能不应该在“原始”中使用实例变量,但这是另一个话题。

    通过这些更改,您将拥有一个不再具有对 self 的强引用的块,并且如果 self 确实确实释放了,它会优雅地处理它。

    最后,我要重申,给 strongSelf 赋值是必要的,以防止在检查 weakSelf 后对象消失的潜在问题。具体...

    if (weakSelf) {
        // Hey, the object exists at the time of the check, but between that check
        // and the very next line, its possible that the object went away.
        // So, to prevent that, you should ALWAYS assign to a temporary strong reference.
        [weakSelf doSomething];
    }
    
    strongSelf = weakSelf;
    // OK, now IF this object is not nil, it is guaranteed to stay around as long as
    // strongSelf lives.
    

    现在,在这种情况下,块是请求的一部分,它是 self 的一部分,所以 self 释放的可能性很小,但我的主要观点是使用 self 来防止保留循环,但是仍然总是通过强引用访问对象——弱强之舞。

    【讨论】:

    • 如果你的代码不能处理weakSelf为nil,我建议重写你的代码而不是做一个强保留。
    【解决方案2】:

    您生成的代码是安全的。阅读 Brad Larson 的回答 here 了解详情。

    【讨论】:

    • 我不明白它究竟是如何打破保留周期的。从技术上讲,它是一个指向同一个对象的指针,它仍然会在我的块中存储为 __strong 对吗?
    • 在捕获弱引用对象时,该块不会进行强引用。事实上,我认为理想的解决方案是让您在块中创建自己对这些对象的强引用,以确保它们随心所欲地存在。
    • @gfrigon - 不,您明确告诉编译器这些是对对象的弱引用,因此它不会将它们保留在块中。如果你让它们保持强大,它会将它们保留在块内。由于您还保留了该块的副本,因此您将获得一个保留周期,因为这些是实例变量(这将导致 self 被隐式保留)。
    • @gfrigon - 过去,我创建了一个小型一次性项目,在该项目中我以这种方式创建了一个基于块的保留循环,并在所涉及的对象的释放上有一个语句日志。它帮助我向自己证明了引入 weak 和 unsafe_unretained 引用可以消除保留周期(泄漏),您可能会发现这样的事情会让您放心。
    • @Brad Larson 谢谢!您的回答让我更好地理解了编译器下面发生的事情。我只是不确定我是否解决了简单隐藏它的问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-02-14
    • 1970-01-01
    • 2010-11-18
    • 1970-01-01
    • 1970-01-01
    • 2021-03-23
    • 2021-09-25
    相关资源
    最近更新 更多