【问题标题】:recursive block and retain cycles in ARCARC中的递归阻塞和保留循环
【发布时间】:2013-11-21 23:01:03
【问题描述】:

编辑2:

没有。建议的答案是关于 async 调用。我想要并且需要同步调用,就像在正常的标准递归调用中一样。

编辑:

同时

__unsafe_unretained void (^unsafe_apply)(UIView *, NSInteger) ;

在没有警告或错误的情况下编译,它在运行时失败,并将 NULL 存储到 unsafe_apply 中。

但是:

- (void) applyToView: (UIView *) view {

    UIColor * (^colorForIndex)(NSInteger) = ^(NSInteger index) {
        return [UIColor colorWithHue: ((CGFloat) index / 255.0f)
                          saturation: 0.5f
                          brightness: 0.5f
                               alpha: 1.0f] ;
    } ;

    void (^applyColors) (UIView *, NSInteger index) = ^(UIView * view, NSInteger index) {
        view.backgroundColor = colorForIndex(index) ;
    } ;

    void (^__block recurse_apply)(UIView *, NSInteger) ;

    void (^apply)(UIView *, NSInteger) = ^(UIView * view, NSInteger level) {
        applyColors(view, level) ;
        [view.subviews enumerateObjectsUsingBlock:^(UIView * subview, NSUInteger idx, BOOL *stop) {
            recurse_apply(subview, 1+level) ;
        }] ;
    } ;

    recurse_apply = apply ;

    apply(view, 0) ;
}

编译没有警告,但更重要的是,实际运行。

但这太丑了!


考虑(着色视图层次结构,用于暴露目的......):

- (void) applyToView: (UIView *) view {

    UIColor * (^colorForIndex)(NSInteger) = ^(NSInteger index) {
        return [UIColor colorWithHue: ((CGFloat) (index * 10.0f) / 255.0f)
                          saturation: 0.5f
                          brightness: 0.5f
                               alpha: 1.0f] ;
    } ;

    void (^applyColors) (UIView *, NSInteger index) = ^(UIView * view, NSInteger index) {
        view.backgroundColor = colorForIndex(index) ;
    } ;

    void (^apply)(UIView *, NSInteger) = ^(UIView * view, NSInteger level) {
        applyColors(view, level) ;
        [view.subviews enumerateObjectsUsingBlock:^(UIView * subview, NSUInteger idx, BOOL *stop) {
            apply(subview, 1+level) ;
        }] ;
    } ;

    apply(view, 0) ;
}

我收到此警告:

/Users/verec/Projects/solotouch/SoloTouch/BubbleMenu.m:551:42:Block pointer variable 'apply' is uninitialized when captured by block

如果我应用建议的修复:Maybe you meant to use __block 'apply'

void (^__block apply)(UIView *, NSInteger) = ^(UIView * view, NSInteger level) {

然后我得到:/Users/verec/Projects/solotouch/SoloTouch/BubbleMenu.m:554:13:Capturing 'apply' strongly in this block is likely to lead to a retain cycle

我尝试了各种方法来篡改代码并消除这些警告

__weak typeof (apply) wapply = apply ;
if (wapply) {
    __strong typeof (wapply) sappy = wapply ;
    wapply(subview, 1+level) ;
}

但事情只会变得更糟,变成错误。

我最终得到了这个:

__unsafe_unretained void (^unsafe_apply)(UIView *, NSInteger) ;

void (^apply)(UIView *, NSInteger) = ^(UIView * view, NSInteger level) {
    applyColors(view, level) ;
    [view.subviews enumerateObjectsUsingBlock:^(UIView * subview, NSUInteger idx, BOOL *stop) {
        unsafe_apply(subview, 1+level) ;
    }] ;
} ;

unsafe_apply = apply ;

apply(view, 0) ;

任何人都有更好的解决方案,我可以在块内做所有事情,而不是像我在这里必须做的那样可怕地打补丁?

注意那些SOQuestions是关于捕获self而那些SOquestions没有任何令人满意的答案。

【问题讨论】:

  • 喜欢我的编辑?但是不得不回补丁真的很丑陋,容易出错。难道没有一些正常的、标准的方式来使用递归块吗?
  • 不!提到的问题是关于 async 调用。我需要常规的、常规的、标准的递归调用。不涉及线程。完全没有。
  • 该解决方案适用于任何递归块。你试过了吗?
  • 你是认真的吗?您指向的“解决方案”有两种方法,20 多行代码,并且 是 异步的!我已经很难过我自己的版本有 3 行不必要的行,你会建议我添加更多的混乱,并且结果取决于 GCD??

标签: objective-c recursion automatic-ref-counting objective-c-blocks retain-cycle


【解决方案1】:

您需要捕获__block 变量,因为块在创建时按值捕获非__block 变量,并且赋值发生在创建块之后。

在 ARC 中,对象指针类型的 __block 变量(通常所有变量都隐式为 __strong)由块保留。因此,如果块捕获指向自身的__block 变量,它将创建一个保留循环。解决方案是让它捕获一个弱引用。在支持__weak 的操作系统版本中,应使用__weak 而不是__unsafe_unretained。

但是,如果 only 对块的引用是__weak 变量,则不会有对块的强引用,这意味着它可以被释放。为了使用块,它必须有一个强引用来保持它。

因此,您需要两个变量,一个弱的,一个强的。在 ARC 中正确的做法是:

__block __weak void (^weak_apply)(UIView *, NSInteger) ;
void (^apply)(UIView *, NSInteger) ;
weak_apply = apply = ^(UIView * view, NSInteger level) {
    applyColors(view, level) ;
    [view.subviews enumerateObjectsUsingBlock:^(UIView * subview, NSUInteger idx, BOOL *stop) {
        weak_apply(subview, 1+level) ;
    }] ;
} ;

apply(view, 0) ;

【讨论】:

  • 当我使用最新版本的 Xcode 尝试此操作时,编译器会用警告“将块文字分配给弱变量;对象将在分配后释放”来标记它。由于您的方法完全可以解决这个问题,我认为忽略该警告是安全的。你知道如何关闭它吗?
  • @Drux:更改了分配顺序
  • 我已经用 XCode 6 对此进行了测试,但是如果我在块内添加分配,内存将不会被释放:|
【解决方案2】:

为了避免 ARC 警告并建立在 @newacct 的答案上,我发现在保留块内设置弱块是可行的:

//a block definition
typedef void (^CompletionType)(NSDictionary * __nullable response, NSError * __nullable error);


//a block calling itself without the ARC retain warning
__block CompletionType completionBlock = nil;
__block __weak CompletionType weakCompletionBlock = nil;
completionBlock = ^(NSDictionary *response, NSError *error) {
    weakCompletionBlock = completionBlock;

        weakCompletionBlock();

    });
};

【讨论】:

  • 但是completionBlock 是一个强保持对象,它本身具有对completionBlock 的强保持引用。所以你有一个保留周期,就像你消除了所有提及weakCompletionBlock一样。
【解决方案3】:

答案是否定的。

我们似乎不能比使用 __block 限定符做得更好。

__block void(^strawberryFields)();
strawberryFields = ^{ strawberryFields(); };
strawberryFields();

感谢 Bill Bumgarner 关于区块的文章。


编辑:

__block __weak void(^strawberryFields)();

似乎是ARC下的首选方式。

【讨论】:

  • 这个 sn-p 早于 ARC;从那时起,__block 的效果发生了变化,在没有 __weak 的情况下使用它会创建一个保留循环:stackoverflow.com/a/9303704
  • __block __weak 收到警告“将块文字分配给弱变量;在 Xcode 5 下分配后对象将被释放......所以这似乎也不是正确的答案。? ??
  • 分两步进行:__weak void (^weak_block)() ; 后跟__block void(^strong_block)() = ^() { if (whatever) weak_block();} ;,最后在strong_block 的正文后面加上:weak_bock = strong_block ;。
猜你喜欢
  • 2012-11-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-26
  • 1970-01-01
  • 2012-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多