[FIXED] rekursive Block- und Retain-Zyklen in ARC

Ausgabe

EDIT2:

Nein. Die vorgeschlagene Antwort bezieht sich auf asynchrone Aufrufe. Ich möchte und brauche synchrone Aufrufe, wie bei einem normalen rekursiven Standardaufruf.

BEARBEITEN:

während

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

ohne Warnung oder Fehler kompiliert wird, schlägt es zur Laufzeit mit einem in unsafe_apply gespeicherten NULL fehl.

Allerdings dies:

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

kompiliert ohne Warnungen, aber was noch wichtiger ist, es wird tatsächlich ausgeführt.

Aber das ist so hässlich !


Betrachten (Einfärben der Ansichtshierarchie, zum Zweck der Darstellung …) :

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

Ich bekomme diese Warnung:

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

Wenn ich den vorgeschlagenen Fix anwende:Maybe you meant to use __block 'apply'

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

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

Ich habe verschiedene Möglichkeiten ausprobiert, um den Code zu manipulieren und diese Warnungen loszuwerden

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

Aber die Dinge werden nur noch schlimmer und verwandeln sich in Fehler.

Ich endete damit:

__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.

Solution

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 werden __blockVariablen vom Typ Objektzeiger (im Allgemeinen sind alle Variablen implizit __strong) vom Block beibehalten. Wenn der Block also eine __blockVariable erfasst, die auf sich selbst zeigt, würde er einen Retain-Zyklus erzeugen. Die Lösung besteht darin, eine schwache Referenz erfassen zu lassen. In Versionen des Betriebssystems, die unterstützen __weak, __weaksollte anstelle von verwendet werden __unsafe_unretained.

Wenn der einzige Verweis auf den Block jedoch eine __weakVariable wäre, gäbe es keine starken Verweise auf den Block, was bedeutet, dass die Zuweisung aufgehoben werden kann. Um den Block verwenden zu können, muss er eine starke Referenz haben, um ihn in der Nähe zu halten.

Daher benötigen Sie zwei Variablen, eine schwache und eine starke. Der richtige Weg, dies in ARC zu tun, ist:

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


Beantwortet von –
newacct


Antwort geprüft von –
Timothy Miller (FixError Admin)

0 Shares:
Leave a Reply

Your email address will not be published. Required fields are marked *

You May Also Like