如何在Objective-C块中引用块对象本身的代码?

17

self只是块内捕获的变量,不引用块本身,那么块如何引用自身而没有明确的捕获变量来实现呢?

5个回答

16
__block void(^strawberryFields)();
strawberryFields = [^{ strawberryFields(); } copy];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),
               strawberryFields);
  • 你使用 __block 是因为在创建块之前就会复制 strawberryFields 的值,这将发生在赋值之前。

  • 在任何其他复制操作之前,你还必须 copy 块,否则你会得到一个引用了栈上原始版本的块。

  • 请注意,上述代码会 泄露该块。 需要在某个地方释放那个块以平衡复制。


1
在某个地方,需要释放资源,为什么不在dispatch_async之后立即释放呢? - user102008
我认为“必须释放一些地方”的意思是需要在某个时候将strawberryFields设置为nil。否则,就会是“草莓田永远存在...”。 - Andrew Rondeau
@AndrewRondeau 这是在ARC真正成为一件事之前写的 :) - bbum

5
我发现这种模式可以在ARC(自动引用计数)中稳定运行,无论是Debug还是Release构建。
-(void) someMethod
{
    // declare a __block variable to use inside the block itself for its recursive phase.
    void __block (^myBlock_recurse)();

    // define the block
    void (^myBlock)() = ^{
        // ... do stuff ...
        myBlock_recurse(); // looks like calling another block, but not really.
    };

    // kickstart the block
    myBlock_recurse = myBlock; // initialize the alias
    myBlock(); // starts the block
}

一开始我尝试只是在myBlock上加上__block修饰符,并直接使用该变量在块的实现中进行递归。这在ARC Debug版本上可以工作,但在Release版本上会出现EXC_BAD_ACCESS错误。另一方面,去掉__block修饰符会引发“通过块捕获时未定义的变量”警告(我不愿意运行并测试它)。


PS:崩溃问题出现在iOS 5.0版本,不确定Mac是否存在该问题。 - adib
你能展示一段代码的例子吗?当你只使用 myBlock 时会崩溃,但是当你使用 myBlock_recurse 时不会崩溃。 - user102008
3
在ARC中,这会导致循环引用。 - newacct
@newacct 临时保留循环 - 确保在递归完成后将myBlock_recurse赋值为nil - adib

0

我以前从未尝试过这个,也不确定它是否有用,但是举个例子:

typedef void (^BasicBlock)(void);

__block BasicBlock testBlock;
testBlock  = ^{NSLog(@"Testing %p", &testBlock);};
testBlock();

你可能需要使用 __block 来声明变量,以防止出现 self-retain 循环。


代码明显有点奇怪,但基本上是正确的。然而,推理是错误的。 - bbum
当然,看我的回答。这段代码并不那么奇怪,只是打印一个__block变量的地址通常会让人感到惊讶(因为有一个隐含的间接引用)。 - bbum
是的,地址确实没有意义。我并不知道块会在赋值之前复制该值,因此无论是否自我保留,__block都是必要的。感谢您的评论。如果您让我再问一件事,当对__block变量进行写入时,块实际上如何处理它?它会引用原始(可能在堆栈中)变量吗? - MHC
一个__block变量实际上是一个句柄,指向数据实际存储的位置。它最初位于堆栈上。当你执行Block_copy()(或-copy)时,它会在堆上分配一小块内存来保存变量的值,复制它,然后更新__block中的指针,使其不再指向堆栈。 - bbum
非常感谢。那么,如果我复制一个块,在其中更改__block变量的值,那么在原始范围内不会看到更改吗? - MHC

0

这个块需要一些方法来清空它自己的引用。通常是通过将块存储在类的属性中来完成的。

有时您可能不想使用属性。以下是如何在没有属性的情况下完成它:

    __weak id weakSelf = self;
    __block id block = ^{

        if(weakSelf) {

            // .. do whatever

            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), dispatch_get_main_queue(), block);
        }
        else {

            block = nil;
        }
    };

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), dispatch_get_main_queue(), block);

重要的是要记住,所有的代码路径都必须导致块= nil。我们通过每5秒调用一次块,直到weakSelf变为nil来实现这一点。

从XCode 5.0(目前处于beta版)开始,这会引发一个警告。这个警告是错误的 - 希望苹果在未来能让这个警告更智能化。 - Dustin
这并不是错误。即使您尝试稍后打破它(我不确定是否正确),您仍然会导致保留循环。更好的解决方案是首先不要有保留循环,通过块捕获对自身的弱引用。 - newacct
如果你持怀疑态度,我建议在XCode中测试代码。将block = nil设置为nil可以在ARC中移除保留的引用。您可以通过在block = nil处设置断点并将指针复制到剪贴板来进行测试。让程序运行一段时间(似乎需要1-5秒),暂停程序,并输入“po”命令查看指针。通过这种方式,您可以轻松地确定对象是否已被销毁。 - Dustin

0
请注意,在ARC中,情况有些不同 - 默认情况下,__block对象指针变量在ARC中是被保留的,而在MRC中则不是。因此,这将导致一个保留循环。为了避免保留循环,块需要捕获对自身的弱引用(使用__weak)。
然而,我们仍然需要在某个地方拥有块的强引用。如果没有强引用,块(由于被复制到堆上)将被释放。因此,我们需要两个变量,一个强引用和一个弱引用,并在块内使用弱引用来引用自身:
__block __weak void(^weakBlock)();
void(^myBlock)();
weakBlock = myBlock = [^{ weakBlock(); } copy];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),
               myBlock);

如果我们不使用 copy,会发生什么? - trss
@trss:编译器的某些版本可能会自动插入一个副本(因此您可能看不到任何区别),但这并不是保证的。如果没有复制,问题就在于weakBlock将指向堆栈块。即使我们稍后使用块的副本(堆块),代码仍然引用指向堆栈块的weakBlock,因此它将执行堆栈块并导致问题。这就是为什么在分配给weakBlock之前必须复制该块的原因。 - newacct
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - puzzl
@puzzl:这个警告是不正确的。它被分配给了一个强引用 myBlock。赋值表达式会计算右侧的结果,并将该结果分配给一个强引用。编译器在赋值的顺序上有错误的挑剔。根据标准,代码是正确的,但我已经改变了它以消除警告。 - newacct

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