verec verec - 4 months ago 26
Objective-C Question

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.

Answer

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