【问题标题】:Is there a SELF pointer for blocks?是否有用于块的 SELF 指针?
【发布时间】:2011-01-28 03:37:24
【问题描述】:

我想从自身内部递归调用一个块。在 obj-c 对象中,我们可以使用“self”,是否有这样的东西可以从自身内部引用块实例?

【问题讨论】:

    标签: objective-c cocoa objective-c-blocks


    【解决方案1】:

    有趣的故事!块实际上是 Objective-C 对象。也就是说,没有公开的 API 来获取块的 self 指针。

    但是,如果在使用块之前声明它们,则可以递归地使用它们。在非垃圾收集环境中,您会执行以下操作:

    __weak __block int (^block_self)(int);
    int (^fibonacci)(int) = [^(int n) {
        if (n < 2) { return 1; }
        return block_self(n - 1) + block_self(n - 2);
    } copy];
    
    block_self = fibonacci;
    

    有必要__block 修饰符应用于block_self,否则,fibonacci 内的block_self 引用会在分配之前引用它(在第一个递归调用)。 __weak 是为了确保块不会捕获对自身的强引用,这会导致内存泄漏。

    【讨论】:

    • 优秀的答案。 __block 修饰符会让我花一些时间自己弄清楚。万分感谢!代表你!
    • 如果你将它传递给一个最终会复制它的函数(例如dispatch_async()),你还需要复制赋值范围内的块。
    • 这段代码会泄露,即使你有终止条件。添加一个倒计时变量并通过 Instruments 泄漏检查器运行它来查看。此外,如果您打开了正确的警告,编译器将发出以下警告:“在此块中捕获 'myBlock' 可能会导致保留周期”
    • @Karl,我想我在写这篇文章时正在使用垃圾收集。那你的解决方案是什么?
    • 今晚或明天我会再试一次。到目前为止,我只设法想出了一个丑陋的 hack。
    【解决方案2】:

    以下递归块代码将使用 ARC、GC 或手动内存管理编译和运行,不会崩溃、泄漏或发出警告(分析器或常规):

    typedef void (^CountdownBlock)(int currentValue);
    
    - (CountdownBlock) makeRecursiveBlock
    {
        CountdownBlock aBlock;
        __block __unsafe_unretained CountdownBlock aBlock_recursive;
        aBlock_recursive = aBlock = [^(int currentValue)
        {
            if(currentValue >= 0)
            {
                NSLog(@"Current value = %d", currentValue);
                aBlock_recursive(currentValue-1);
            }
        } copy];
    #if !__has_feature(objc_arc)
        [aBlock autorelease];
    #endif
    
        return aBlock;
    }
    
    - (void) callRecursiveBlock
    {
        CountdownBlock aBlock = [self makeRecursiveBlock];
    
        // You don't need to dispatch; I'm doing this to demonstrate
        // calling from beyond the current autorelease pool.
        dispatch_async(dispatch_get_main_queue(), ^
                       {
                           aBlock(10);
                       });
    }
    

    重要注意事项:

    • 您必须手动将块复制到堆上,否则当您从另一个上下文调用它时,它会尝试访问不存在的堆栈(ARC 通常会为您执行此操作,但并非在所有情况下都这样做。最好安全地使用它)。
    • 您需要两个引用:一个保存对块的强引用,另一个保存要调用的递归块的弱引用(从技术上讲,这只需要 ARC)。
    • 您必须使用 __block 限定符,以便块不会捕获块引用的尚未分配的值。
    • 如果您要进行手动内存管理,则需要自己自动释放复制的块。

    【讨论】:

    • 这在 ARC 中是行不通的,但是 copy] autorelease] 不只是复​​制,所以你不必在块内调用 release 呢?对于 ARC 可能会在调用 aBlock(10); 之后执行 aBlock = nil
    • 实际上,在重新访问此解决方案时,事实证明,如果未调用该块,则将引用保存在块内会导致泄漏。我现在已经更新了答案,无论您是否调用它,它都能正常工作。
    • 我的问题增加了一层复杂性,但我不完全确定我的问题是否可以解决。我希望能够通过 AFNetworking 重试 API 调用。我认为这个 pastebin 简化并说明了问题。 pastebin.com/xRPJuckZ unsafe_unretained 递归块是从其自身内部的另一个块内部调用的,在那里调用它会导致错误的访问异常。你知道如何解决这个问题吗?
    • aBlock 是持有强引用的那个,所以一旦它超出范围(在你返回它的调用函数中),它就会被释放,并且你的内部 unsafe_unretained 引用变得无效。您需要设计一种方法来保存强引用,直到您确定不再需要它为止。您也许可以执行“__block RetryAPICall aBlock”,然后在块内有条件地将其设置为 nil。不过,我不确定这是否可行。
    【解决方案3】:

    您必须将块变量声明为__block

    typedef void (^MyBlock)(id);
    
    __block MyBlock block = ^(id param) {
      NSLog(@"%@", param);
      block(param);
    };
    

    【讨论】:

    【解决方案4】:

    区块(目前)还没有self。您可以像这样构建一个(假设为 ARC):

    __block void (__weak ^blockSelf)(void);
    void (^block)(void) = [^{
            // Use blockSelf here
    } copy];
    blockSelf = block;
        // Use block here
    

    __block 是必需的,因此我们可以在创建块后将blockSelf 设置为块。 __weak 是必需的,否则该块将持有对自身的强引用,这将导致强引用循环并因此导致内存泄漏。需要copy 以确保将块复制到堆中。对于较新的编译器版本,这可能是不必要的,但不会造成任何伤害。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-04
      • 2010-09-22
      • 2023-03-04
      相关资源
      最近更新 更多