16

behalten brechen besser Um die Frage zu veranschaulichen, betrachten Sie die folgende vereinfachte Form Blockrekursion:Blockrekursion und Zyklus

__block void (^next)(int) = ^(int index) { 
    if (index == 3) { 
     return; 
    } 
    int i = index; 
    next(++i); 
}; 
next(0); 

XCode (ARC-fähige) warnt davor, dass "Capturing 'next' stark in diesem Block wahrscheinlich zu einem Rückhaltezyklus führen ".

Einverstanden.

Frage 1: Würde das behalten Zyklus erfolgreich, indem der Block selbst zu nil, auf diese Weise gebrochen werden:

__block void (^next)(int) = ^(int index) { 
    if (index == 3) { 
     next = nil; // break the retain cycle 
     return; 
    } 
    int i = index; 
    next(++i); 
}; 
next(0); 

(Anmerkung: Sie würden immer noch die gleiche Warnung erhalten, aber vielleicht ist es ist ungerechtfertigt)

Frage 2: Was wäre eine bessere Implementierung der Blockrekursion?

Danke.

+0

Warum wird der __block trotzdem deklariert? Es wird nicht erfasst. –

+0

@Catfish_Man, es ist '__block int i', weil es in der nächsten Codezeile geändert wird. – krisk

+0

Dieser Code könnte möglicherweise nicht funktionieren; next wird erst zugewiesen, wenn der Block kompiliert ist. Daher wird der Aufruf von next innerhalb des Blocks abstürzen. –

Antwort

4

Um die rekursive Blockausführung ohne Retain-Zyklus zu erreichen, müssen Sie zwei Blockreferenzen verwenden - einen schwachen und einen starken. Also für Ihren Fall, das ist, was der Code aussehen könnte:

__block __weak void (^weak_next)(int); 
void (^next)(int); 
weak_next = next = ^(int index) { 
    if (index == 3) { 
    return; 
    } 
    int i = index; 
    weak_next(++i); 
}; 
next(0); 

Beachten Sie, dass der Block die schwache Blockreferenz erfaßt (weak_next) und der externe Kontext fängt die starke Referenz (Weiter), um den Block herum zu halten . Beide Referenzen verweisen auf denselben Block.

Ein weiteres Beispiel für dieses Muster, das auch Blockrekursion verwendet, finden Sie unter https://stackoverflow.com/a/19905407/1956124. Darüber hinaus ist die Diskussion im Kommentarbereich des folgenden Artikels auch hier relevant: http://ddeville.me/2011/10/recursive-blocks-objc/

1

Ich denke @newacct ist korrekt über @Matt Wilding-Lösung; Es sieht so aus, als ob in diesem Fall nichts einen starken Verweis auf den nächsten Block haben wird und zu einer Laufzeitausnahme führen wird (zumindest für mich).

Ich weiß nicht, wie häufig es ist, rekursiv aufgerufene Blöcke in der Wildnis in objc zu finden. Doch in einer realen Welt-Implementierung (wenn tatsächlich erforderlich) auf sagen sie, ein View-Controller, könnte man den Block definieren und dann eine interne Schnittstelle Eigenschaft mit einem starken Bezug gesetzt bis zu dem Block:

typedef void(^PushButtonBlock)(); 

@interface ViewController() 
@property (strong, nonatomic) PushButtonBlock pushButton; 
@end 

@implementation ViewController 
    ... 
    // (in viewDidLoad or some such) 
    __weak ViewController *weakSelf = self; 

    self.pushButton = ^() { 
    [weakSelf.button pushIt]; 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), weakSelf.pushButton); 
    }; 

    self.pushButton(); 
    ... 
@end 

Das läuft gut für mich und hat keine Compiler-Warnungen über Retain-Zyklen (und keine Lecks in Instrumenten). Aber ich denke, ich würde wahrscheinlich davon ablenken (rekursive Blockaufrufe) in den meisten Fällen in Objc - es ist stinkend. Aber auf jeden Fall interessant.