Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

recursive block and retain cycles in ARC

EDIT2:

No. The suggested answer is about async calls. I want & need synchronous calls, like in a normal, standard recursive call.

EDIT:

while

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

compiles without warning or errors, it fails at runtime with a NULL stored into unsafe_apply.

However this:

- (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) ;
}

compiles without warnings, but more importantly, actually runs.

But this is so ugly!


consider (colouring the view hierarchy, for exposing purpose ...):

- (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) ;
}

I get this warning:

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

If I apply the suggested fix: Maybe you meant to use __block 'apply'

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

I then get: /Users/verec/Projects/solotouch/SoloTouch/BubbleMenu.m:554:13: Capturing 'apply' strongly in this block is likely to lead to a retain cycle

I tried various ways to tamper with the code and get rid of those warnings

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

But things just get worse, turning into errors.

I ended up with this:

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

Anyone has a better solution, where I could do everything from within the block and not hideously back patch it as I had to do here?

Note Those SO Questions are about capturing self and those SO questions don't have any satisfactory answer.

like image 817
verec Avatar asked Nov 09 '13 23:11

verec


2 Answers

You need to capture a __block variable, because blocks capture non-__block variables by value when they are created, and the assignment happens after the block has been created.

In ARC, __block variables of object pointer type (generally all variables are implicitly __strong) are retained by the block. So if the block captures a __block variable pointing to itself, it would create a retain cycle. The solution is to have it capture a weak reference. In versions of the OS that support __weak, __weak should be used instead of __unsafe_unretained.

However, if the only reference to the block was a __weak variable, there would be no strong references to the block, which means it can be deallocated. In order to use the block, it must have a strong reference to keep it around.

Therefore, you need two variables, one weak and one strong. The proper way to do it in ARC is:

__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) ;
like image 200
newacct Avatar answered Nov 13 '22 21:11

newacct


The answer is no.

We can't seem to do better than using the __block qualifier.

under ARC

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

pre ARC

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

Thanks to Bill Bumgarner article about blocks.

like image 25
verec Avatar answered Nov 13 '22 19:11

verec