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];

另外,在下面的函数中,我需要使用[块复制]吗?

- (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

4 回答 4

5

与其实施,不如-performBlock:afterDelay:使用dipatch_after(). 除其他外,这不是传递给对象的消息,因此毫无疑问将其定位于哪个接收者。

实际上,这里根本没有内存管理问题。当一个对象保留一个块并且该块(可能隐含地)保留同一个对象时,通常只需要执行“弱自我”方法。但是,该对象并未保留该块。它一直由框架保留,直到-performSelector:withObject:afterDelay:发生火灾,但这不是保留周期。

如果存在保留周期,则不应self在块中引用。因此,您的嵌套案例在调用消息时是错误的,self而不是weakSelf.

最后,是的,[block copy]当您在执行离开其声明范围后保留一个块时,或者如果您将其传递给非块特定 API 时,您确实需要这样做。也就是说,当您将块传递给时,您不需要复制它,dispatch_async()因为这是一个块感知 API,它知道在必要时制作自己的副本。但-performSelector:withObject:afterDelay:不是块感知的。它只是将其参数视为通用对象并保留它。因此,您必须在将块传递给该块时复制它。

于 2013-03-16T11:38:48.090 回答
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];
于 2013-03-17T10:48:53.260 回答
0

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

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

不幸的是,在某些情况下(实际上经常如此)该块由创建它的实例保留,并且它自己保留该实例。这称为保留循环,除非您自己破坏其中一个保留关系,否则您的对象和块无法释放。例如,如果您使用实例变量引用您的块并且您不手动取消它,则可能会发生这种情况。

这可能是块的主要问题,尤其是因为有时,您不知道您的块保留了您的自我实例(例如,您的块中的 NSAssert)。然后:

  • 如果您立即执行您的块并释放它(在执行后使用您的块和调度释放它),则没有风险,因为您确定 self 引用的对象仍然存在。

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

  • 关于复制。是的,如果您的块作为方法参数传递,则使用复制会更安全,以确保您在此范围内有一个干净的独占块,并且保留计数为 +1。但也许 ARC 已经为您做到了。不确定。例如它 performWithSelector 似乎是免费的,那么复制并不危险。只是一个没用的。有时编译器可以通过删除它来优化它,但必须检查它。

于 2013-03-16T11:52:28.293 回答
-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];
    })
});

我发现它比冗长的默认阅读更好一些。

希望这可以帮助 :)

于 2013-03-16T11:36:08.513 回答