递归块和ARC中的保留循环

22

编辑2:

不。建议的答案是关于异步调用。我想要并需要同步调用,就像普通的标准递归调用一样。

编辑:

当......

__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: 当被块捕获时,块指针变量'apply'未初始化

如果我应用建议的修复方法:也许你想使用__block 'apply'

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

我得到了:/Users/verec/Projects/solotouch/SoloTouch/BubbleMenu.m:554:13: 在此块中强引用'apply'可能会导致循环引用

我尝试了各种方法来修改代码并解决这些警告。

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

有没有更好的解决方案,可以在代码块内部完成所有操作而不像我现在这样让它过于丑陋地进行回溯修补

请注意那些SO问题是关于捕获self的,而那些SO问题没有任何令人满意的答案。


不!所提到的问题是关于_async_调用的。我需要常规的、普通的、标准的递归调用。没有任何线程参与。 - verec
这个解决方案适用于任何递归块。你试过吗? - jscs
@Rob Deville的帖子使用了2K字来展示底部适合5行的解决方案,而且正是我想要摆脱的(使用__block限定符)...更不用说他对copy的使用是多余的了... - verec
罗布·德维尔是谁?我通过一个名为“Berik”的用户的回答链接来回答你。与其与我争论,为什么不直接解决您的问题呢? - jscs
用户@rob给出了这个链接:http://ddeville.me/2011/10/recursive-blocks-objc/,但显然他已经删除了评论。我正在回复他。 - verec
显示剩余5条评论
3个回答

64

你需要捕获一个 __block 变量,因为block在创建时通过值来捕获非__block变量,而且赋值发生在block创建之后。

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

然而,如果对于block唯一的引用是一个 __weak 变量,那么就没有强引用指向block,这意味着它可以被释放。为了使用block,它必须有一个强引用来保持它的存在。

因此,你需要 两个 变量,一个是弱引用,一个是强引用。在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) ;

1
当我尝试在最新版本的Xcode中运行此代码时,编译器会发出警告:“将块文字分配给弱变量;对象将在分配后被释放”。由于您的方法正好解决了这个问题,我认为可以安全地忽略这个警告。您知道如何关闭它吗? - Drux
4
@Drux:更改了任务分配顺序 - newacct
我已经使用XCode 6进行了测试,但是如果我在块内添加一个分配,内存将不会被释放 :| - Lord Zsolt

1
答案是否定的。
我们似乎无法找到比使用__block限定符更好的方法。
在ARC下。
__block __weak void(^weakStrawberryFields)();
__block void(^strawberryFields)() = ^() { weakStrawberryFields(); };
weakStrawberryFields = strawberryFields;
strawberryFields();
ARC 前
__block void(^strawberryFields)();
strawberryFields = ^{ strawberryFields(); };
strawberryFields();

感谢Bill Bumgarner关于块的文章。


4
这段代码早于自动引用计数(ARC)出现;__block 的作用自那时起已经改变,如果没有使用__weak,将会创建循环引用:https://dev59.com/Imoy5IYBdhLWcg3wieu6#9303704/ - jscs
@jscs答案已更新以适应ARC。 - Cœur

0
为了避免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();

    });
};

3
然而,completionBlock是一个强引用的对象,它本身又强引用了completionBlock。因此,你得到了一个保留循环,就像你消除了所有提到weakCompletionBlock一样。 - Tommy

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