【问题标题】:How can code inside an Objective-C block reference the block object itself?Objective-C 块中的代码如何引用块对象本身?
【发布时间】:2011-03-05 02:38:33
【问题描述】:

self 只是块内的捕获变量,并不引用块本身,那么块如何在没有明确捕获变量的情况下引用自身呢?

【问题讨论】:

    标签: objective-c objective-c-blocks


    【解决方案1】:
    __block void(^strawberryFields)();
    strawberryFields = [^{ strawberryFields(); } copy];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),
                   strawberryFields);
    
    • 您使用__block,因为该块将在创建块时复制strawberryFields 的值,这将在分配之前

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

    • 请注意,上面的代码泄露了区块。在某个地方,需要该块的release 来平衡副本。

    【讨论】:

    • “某处,需要发布”为什么不在dispatch_async之后?
    • 我认为“某处,需要发布”意味着在某些时候需要将草莓字段设置为 nil。否则,它就是“永远的草莓地……”
    • @AndrewRondeau 这是在 ARC 真正出现之前编写的。 :)
    【解决方案2】:

    我发现这种模式对于 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
    }
    

    最初我尝试将__block 修饰符添加到myBlock 并直接使用该变量在块的实现中递归。这适用于 ARC Debug 版本,但在 Release 版本中与 EXC_BAD_ACCESS 中断。另一方面,删除 __block 修饰符会引发“在被块捕获时未定义变量”警告(我不愿意运行它并进行测试)。

    【讨论】:

    • PS:崩溃是在 iOS 5.0 中,不确定 Mac。
    • 您能否展示一个代码示例,当您使用 myBlock 时会崩溃,但在使用 myBlock_recurse 时不会崩溃?
    • 在 ARC 中,这会导致一个保留周期
    • @newacct 临时保留周期 - 只需确保在递归完成后将 nil 分配给 myBlock_recurse
    【解决方案3】:

    我以前从未尝试过,也不是 100% 确定它是否有用,如果有效,但例如:

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

    您可能已经使用 __block 声明变量以防止自保留循环。

    【讨论】:

    • 代码确实很奇怪,但或多或​​少是正确的。但是,这个推理是不正确的。
    • 当然;看我的回答。代码并没有那么奇怪,真的,只是打印__block 变量的地址通常令人惊讶(因为存在隐含的间接)。
    • 是的,地址确实没有任何意义。而且我不知道该块会在分配 之前 复制值,因此无论自我保留如何,__block 都是必要的。感谢您的评论。如果你让我再问一件事,block 在写入时实际上是如何处理 __block 变量的?它是否引用了原始(可能在堆栈中)变量?
    • 一个__block 变量实际上是一个句柄;指向数据实际保存位置的指针。它从堆栈开始。当您执行 Block_copy()(或 -copy)时,它会在堆上分配一点内存来保存变量值,复制它,然后更新 __block 中的指针以不再指向堆栈。
    • 非常感谢。然后,如果我复制一个块,在其中更改 __block 变量的值,那么更改在原始范围内不可见?
    【解决方案4】:

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

    有时您可能宁愿不使用属性。以下是没有属性的情况:

        __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);
    

    要记住的关键是所有代码路径都必须指向 block = nil。我们在这里每 5 秒调用一次块,直到 weakSelf 变为 nil。

    【讨论】:

    • 从 XCode 5.0(目前处于测试阶段)开始,这会引发警告。警告是错误的——希望 Apple 将来能更智能地处理这个警告。
    • 没有错。您正在导致一个保留周期(即使您稍后尝试打破它,我不确定这是否正确)。更好的解决方案是一开始就没有保留周期,让块捕获对自身的弱引用。
    • 如果您持怀疑态度,我建议您在 XCode 中测试代码。设置 block = nil 删除弧中的保留参考。您可以测试它在 block = nil 处设置断点并将指针复制到粘贴板中的块。让程序运行一段时间(似乎需要 1-5 秒),暂停程序,然后 po 指针。通过这种方式,您可以轻松判断对象已被销毁。
    【解决方案5】:

    请注意,在 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:某些版本的编译器可能会自动插入一个副本(因此您可能看不到任何区别),但这不能保证。如果没有复制,问题是weakBlock 将指向堆栈块。而且即使我们以后可能会使用块的副本(堆块),在块内部,代码仍然引用weakBlock,它指向堆栈块,因此它会执行堆栈块并导致问题。这就是为什么在分配给weakBlock之前必须复制块的原因。
    • 这不起作用。它给出了一个关于分配给弱变量(weakBlock)的警告,警告是正确的:weakBlock is deallocate immediate.
    • @puzzl:警告不正确。它被分配给myBlock,这是一个强引用。赋值表达式计算右侧的结果,并将该结果分配给强引用。编译器对赋值的顺序有错误的挑剔。根据标准,代码是正确的,但我已经更改它以消除警告。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多