块中嵌套块,使用__weak self

4

我正在尝试确定我是否做得正确:

如果我只有一个块,我会这样做:

__weak MyClass *weakSelf = self;  
[self performBlock:^{                 //<< Should I use self, or weakSelf here?

    [weakSelf doSomething];

} afterDelay:delay];

但是如果一个块中还有一个块会发生什么?这样做是否正确?

__weak MyClass *weakSelf = self;
[self performBlock:^{

    [weakSelf doSomething];

    [self performBlock:^{

        [weakSelf doSomething]; 
    } afterDelay:1.0f];

} afterDelay:delay];

还有,在下面的函数中,我需要使用[block copy]吗?

- (void)performBlock:(void (^)(void))block afterDelay:(float)delay
{
    if (block)
    {
        if (delay > 0)
        {
            [self performSelector:@selector(executeBlockAfterDelay:) withObject:[block copy] afterDelay:delay];
        }
        else
        {
            [self executeBlockAfterDelay:[block copy]];
        }
    }
}

- (void)executeBlockAfterDelay:(void(^)(void))block
{
    if (block)
        block();
}
4个回答

5
在这种情况下(以下),只需使用 strong self ,因为该块只被复制了几秒钟。通常,如果您希望 self 执行块,则希望它在此期间保持活动状态,因此强引用是完全可以的。
[self performBlock:^{
    [self doSomething]; // strong is OK
} afterDelay:delay];

块内嵌块?在您的情况下,这两个块只是延迟单次块,所以与上面相同,使用strong。但是块之间存在差异。如果您将块存储更长时间,可能需要多次调用,则应避免保留周期。

示例:

self.callback = ^{
    [self doSomething]; // should use weakSelf
};

这可能会导致保留循环。实际上,这取决于块的使用方式。我们发现,该块被存储(复制)在属性中以便后续使用。不过,您可以通过将不再使用的块置空来防止保留循环。在这种情况下:

self.callback(); //invoke
self.callback = nil; //release

使用ARC时,您无需自己复制代码块。在添加代码块后的早期版本中存在错误,但现在ARC下的编译器知道何时复制代码块。它足够聪明,在这种情况下会自动复制:

[self performSelector:@selector(executeBlockAfterDelay:) withObject:block afterDelay:delay];

5
不要使用 -performBlock:afterDelay:,而是使用 dispatch_after()。这样做的好处之一是,它不会向对象发送消息,因此就不存在针对哪个接收者进行目标定位的问题。
实际上,这里根本没有内存管理问题。通常情况下,只有在一个对象保留了一个块且该块(可能隐式地)保留了同一对象时,才需要采用“弱引用self”的方法。然而,在这里,对象并没有保留块。它被框架保留,直到 -performSelector:withObject:afterDelay: 被触发,但这不构成一个保留循环。
如果存在保留循环,则不应该在块中引用 self。因此,你嵌套的情况是错误的,因为它调用了一个消息,而不是使用 weakSelf
最后,确实需要在块在声明范围之外执行或将其传递给非块特定的API时,使用 [block copy]。也就是说,当你将块传递给诸如 dispatch_async() 之类的API时,无需复制它,因为那是一个块感知的API,知道在必要时自己制作副本。但 -performSelector:withObject:afterDelay: 不是块感知的。它只将其参数视为通用对象并保留它。因此,当将其传递给该方法时,必须复制该块。

1
我刚做了一个快速测试,似乎-performSelector:withObject:afterDelay:实际上是复制了这个块。 - Martin R
但我找不到关于那个的参考/文档。通常,-performSelector:withObject:afterDelay: 会保留 anObject 参数,而不是复制它。 - Martin R
@MartinR:不仅在将其传递给函数时会发生这种情况,较新版本的编译器似乎会在定义后立即复制该块,或者类似于此。但这只是实现细节。旧版本的编译器没有这样做,它们仍然是正确的。您不应该依赖它。 - newacct
回答他的问题,不需要在 [self executeBlockAfterDelay:[block copy]] 中加入 [block copy](该方法名称取错了--它会立即执行),但是当传递给 performSelector: 时需要加入。 - newacct
@newacct:是的,我总是为块使用@property(copy)。但是知道确切的行为(取决于编译器、iOS/OS X版本、部署目标、ARC等)会很好。似乎有很多混淆,从“始终复制块”到“使用ARC时不需要复制块”。 - Martin R

0

关于块的最重要的一点是,它们捕获了一段代码(包括值),并将其作为抽象实体进行操作,可以将其保留在某个地方、传递、复制等。实际上,它的实现方式保证了默认情况下您的块将保持有效,并且稍后可以安全地执行。

然后,在块内捕获和保留所需的依赖项是必要的。

不幸的是,在某些情况下(实际上相当频繁),块由创建它的实例保留,并且它自己也保留该实例。这被称为保留循环,并使您的对象和块无法解除分配,除非您自己打破其中一个保留关系。例如,如果您使用实例变量引用块,并且没有手动将其设置为nil,则可能会发生这种情况。

这可能是块的主要问题,特别是因为有时候您不知道您的块是否保留了自身实例(例如,在您的块中使用NSAssert)。因此:

  • 如果您立即执行并释放块(使用dispatch release it after execution),则没有风险,因为您确定self引用的对象仍然存在。

  • 但是,如果执行被延迟,则重要的是在块内保留您的对象。但在这种情况下,您的对象不应保留您的块,以避免保留循环(A保留B,B保留A)。如果您在方法的私有范围内定义和可选引用您的块,则完美。

  • 关于复制。是的,如果将您的块作为方法参数传递,使用copy可能更安全,以确保在此范围内具有干净的独占块,并具有+1 retainCount。但也许ARC已经为您做到了这一点。对此不太确定。例如,它似乎可以免费执行performWithSelector,那么复制就不危险了。只是无用的。有时编译器可以通过删除它来优化它,但必须进行检查。


-3

我通常这样做:

__unsafe_unretained __block id blockSelf = self;

然后在我的代码块中使用它,没有问题。

所以在你的情况下:

__unsafe_unretained __block MyClass *blockSelf = self;
[self performBlock:^{
    [weakSelf doSomething];
    [self performBlock:^{
        [weakSelf doSomething]; 
    } afterDelay:1.0f];
} afterDelay:delay];

另外为了让你的生活更轻松一些 - 创建一个工具类并将其放在头文件中

void RunAfterDelay(NSTimeInterval delayInSeconds, dispatch_block_t block);

然后在 .m 文件中添加这个

void RunAfterDelay(NSTimeInterval delayInSeconds, dispatch_block_t block)
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC), dispatch_get_main_queue(), block);
}

将这些实用工具导入您的前缀,然后您就可以开始了:

__unsafe_unretained __block MyClass *blockSelf = self;
RunAfterDelay(1.0f,^{
    [blockSelf doSomething];
    RunAfterDelay(delay,^{
        [blockSelf doSomething];
    })
});

我觉得这比默认的啰嗦的代码更易读。

希望这能有所帮助 :)


谢谢您的回复。为什么您使用__unsafe_unretained而不是__weak? - BlackMouse
两件事:__unsafe_unretained虽然可以防止与self相关的保留循环,但不会自动置空,从而大大增加了块引用悬空指针的可能性,而不仅仅是nil。另外,blockSelf的总是放在第一个块中,否则你就失去了使用blockSelf的好处(因为它们是隐式的__strong对象),尽管编译器不会抱怨。 - CodaFi
首先,在 ARC 下,__block 的唯一作用是能够对变量进行赋值。由于变量 blockSelf 没有被赋值到任何地方,因此使用 __block 是没有意义的。其次,CodaFi 是正确的,通常应避免使用 __unsafe_unretained,除非必要,除非您正在部署 iOS 4,其中无法使用 __weak。 - newacct
抱歉 - 我的项目仍支持iOS 4,我完全没有提到这一点。请参考评论和其他答案,了解使用__unsafe_unretained与__weak的相关信息。 - fatuous.logic

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