iOS块 - 避免保留循环

3

如果我在块内使用dispatch_queue,那么应该如何避免保留循环但又避免过早释放弱指针?

    __weak MyClass *weakSelf = self;

    [apiClient fetchData:^(...) {
        typeof(self) selfref = weakSelf;

        dispatch_async(dispatch_get_main_queue(), ^{
            // using selfref here
        }
    });

这样做是正确的吗?我是否遗漏了什么?如何确保一切都被正确处理且没有循环引用发生?我无法在此处进行dealloc测试...

我的问题不同于这里,因为我在第一个块中有另一个块。我想知道如何处理这种情况。


这方面有很多问题。请搜索相关内容。保留周期的关键是“cycle”这个词。如果A引用B,而B又引用A,那么你就会遇到麻烦。但这里似乎不是这种情况。你可能需要检查selfref != nil(我更喜欢将变量命名为weakSelf / strongSelf,这样可以更清晰地表达意思),除此之外,代码本身是没问题的。 - gnasher729
@swalkner,请在此处查看我的答案:http://stackoverflow.com/a/29343651/1641848 - Razvan
可能是Can __weak self turn nil in the middle of the block?的重复问题。 - Razvan
3个回答

10

首先需要澄清的是,只有当对象指向自身时才会出现保留环!这通常发生在对象的某个属性(obj持有该属性,该属性又持有obj等)通过块或委托强引用回对象本身。

块从外部作用域捕获传递给它们的任何对象的强引用 - 包括self。 当你声明一个块并且只执行它时并不会造成问题,因为块是在方法内声明的,ARC将在方法结束时对其进行释放:

-(void)test
{
    void(^aBlock)() = ^{

        [self someMethod];

    };

    aBlock();
}

但是,如果您将此块存储以供稍后使用(例如回调),则会出现问题,例如:

@property(nonatomic, copy) void(^aBlock)();

-(void)test
{
    _aBlock = ^{

        [self someMethod];

    };
}

现在我们在ClassA中有了一个强引用(我们的属性aBlock),它通过aBlock实现对ClassA自身(self)的强引用。现在我们遇到了一个问题 - 这个类指向它自己,永远不会被释放!如果我们将上述重写为以下形式,情况就更明显了:

-(void)test
{
    self.aBlock = ^{

        [self someMethod];

    };
}

现在让我们分解你的代码,以确保我们谈论的是相同的东西:

__weak MyClass *weakSelf = self;

[apiClient fetchData:^(...) {
    typeof(self) selfref = weakSelf;  <---- outer block

    dispatch_async(dispatch_get_main_queue(), ^{
        // using selfref here   <---- inner block
    }
                   });

按步骤进行:

  1. 我们声明 weakSelf,这样在外部块中使用它时,它不会将强引用存储到 self 中 - retainCount保持不变
  2. 我们通过捕获它的本地强变量来确保 weakSelf 在外部块结束前可用(如果您想知道为什么会发生这种情况,请注意 self 既不是 weak 也不是 strong - 它是 unsafe_unretained)。该本地强变量将在外部作用域结束时取消引用。因此,在外部块的开头只会增加+1,并在结尾处减少-1 - 没有块外的任何更改。
  3. 内部块将捕获对本地强引用的强引用(+1),但内部块本身是在外部块中声明的(将其视为变量,或查看第一个“test”场景),因此它将在安全释放外部块的末尾被释放。释放内部块将导致安全释放它所持有的本地强引用的引用,因此-1 引用计数 - 再次没有块范围外的任何更改。

希望您能明白所有这些,并与 ObjC 块战斗得光荣!


0

使用以下代码是管理block的最佳方式。

 __weak MyClass *weakSelf = self;

    [apiClient fetchData:^(...) {
        __strong MyClass * selfref = weakSelf;

        dispatch_async(dispatch_get_main_queue(), ^{
            // using selfref here
        }
    });

__strong 关键字的作用是保证在 block 执行期间,如果 weakSelf 还存在,则它将继续存在。因此,如果您在另一个 block 中使用该变量,也不会有任何问题,因为 main blockinner block 执行时已经完成了。

typeof(self) selfref = weakSelf; 相当于 __strong MyClass * selfref = weakSelf;

希望这可以帮助您。


1
这正是他发布的代码。__strong 是默认值,typeof (self) 和 MyClass* 在这里是相同的。 - gnasher729
@gnasher729,是的,但有时候,初始程序员无法确定确切的语法,这就是为什么我写了那个的原因。 - Jatin Patel - JP
所以这里没有语法问题。问题是提问者写的语法是否正确?答案是正确的。 - Jatin Patel - JP

0
据我所知,当您使用块时,不应使用强引用。但是,如果您的代码需要“self”,则可以像您的情况一样声明弱属性。我认为,您的代码运行得非常好。
为了确保,您可以轻松测试覆盖dealloc方法,就像下面这样。
-(void)dealloc{
    NSLog(@"Deallocated");
}

请复制并粘贴以下代码片段,然后运行您的代码。如果在控制台中看到“Deallocated”一词,则说明您没有保留周期。您还可以从配置文件中监视自己是否处于保留周期中。

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