2013-01-08 4 views
6

Ich habe mehrere andere Fragen der gleichen Form gesehen, aber ich entweder a) kann nicht verstehen, die bereitgestellten Antworten, oder b) nicht sehen wie diese Situationen meinen ähneln.Objective-C Block "Retain-Zyklus" Warnung, verstehe nicht, warum

Ich schreibe eine Kategorie auf UIView, um rekursiv alle Untersichten einer UIView auszuwerten und ein Array von Subviews zurückzugeben, die einen Test bestehen. Ich habe festgestellt, wo mein Compiler-Warnung auftritt:

-(NSArray*)subviewsPassingTest:(BOOL(^)(UIView *view, BOOL *stop))test { 

    __block BOOL *stop = NO; 

    NSArray*(^__block evaluateAndRecurse)(UIView*); 
    evaluateAndRecurse = ^NSArray*(UIView *view) { 
     NSMutableArray *myPassedChildren = [[NSMutableArray alloc] init]; 
     for (UIView *subview in [view subviews]) { 
      BOOL passes = test(subview, stop); 
      if (passes) [myPassedChildren addObject:subview]; 
      if (stop) return myPassedChildren; 


      [myPassedChildren addObjectsFromArray:evaluateAndRecurse(subview)]; 
      // ^^^^ Compiler warning here ^^^^^ 
      // "Capturing 'evaluateAndRecurse' strongly in this block 
      // is likely to lead to a retrain cycle" 
     } 
     return myPassedChildren; 
    }; 

    return evaluateAndRecurse(self); 
} 

Auch ich bekomme einen bad_access Fehler, wenn ich beinhalten nicht die __block Modifikator in meinem Block Erklärung (^__block evaluateAndRecurse). Wenn jemand erklären könnte, warum das so ist, wäre das auch sehr hilfreich. Vielen Dank!

Antwort

8

Das Problem hier ist, dass Ihr Block evaluteAndRecurse() sich selbst erfasst, was bedeutet, dass, wenn es jemals kopiert werden soll (ich glaube nicht, wird es in Ihrem Fall, aber in etwas weniger trivialen Fällen kann es), dann es wird sich selbst behalten und daher für immer leben, da es nichts gibt, was den Rückhaltezyklus durchbrechen könnte.

Bearbeiten: Ramy Al Zuhouri machte einen guten Punkt, mit __unsafe_unretained auf den einzigen Verweis auf den Block ist gefährlich. Solange der Block auf dem Stapel verbleibt, funktioniert dies. Wenn der Block jedoch kopiert werden muss (z. B. muss er in einen übergeordneten Bereich zurückverwandelt werden), wird die Zuweisung durch den Code __unsafe_unretained aufgehoben. Der folgende Absatz wurde mit dem empfohlenen Ansatz aktualisiert:

Wahrscheinlich möchten Sie hier eine separate Variable verwenden, die mit __unsafe_unretained markiert ist, die auch den Block enthält, und diese separate Variable erfassen. Dies verhindert, dass es sich selbst zurückhält. Sie könnten __weak verwenden, aber da Sie wissen, dass der Block am Leben sein muss, wenn er aufgerufen wird, müssen Sie sich nicht mit dem (sehr geringen) Overhead einer schwachen Referenz befassen. Dadurch wird Ihr Code aussehen wie

NSArray*(^__block __unsafe_unretained capturedEvaluteAndRecurse)(UIView*); 
NSArray*(^evaluateAndRecurse)(UIView*) = ^NSArray*(UIView *view) { 
    ... 
     [myPassedChildren addObjectsFromArray:capturedEvaluateAndRecurse(subview)]; 
}; 
capturedEvaluateAndRecurse = evaluteAndRecurse; 

Alternativ machen Sie einen Zeiger auf den Block erfassen könnte, die die gleiche Wirkung haben, sondern ermöglicht es Ihnen, statt nach den Zeiger vor dem Block Instanziierung zu greifen. Dies ist eine persönliche Präferenz. Es erlaubt Ihnen auch die __block wegzulassen:

NSArray*(^evaluateAndRecurse)(UIView*); 
NSArray*(^*evaluteAndRecursePtr)(UIView*) = &evaluateAndRecurse; 
evaluateAndRecurse = ^NSArray*(UIView*) { 
    ... 
     [myPassedChildren addObjectsFromArray:(*evaluateAndRecursePtr)(subview)]; 
}; 

Was benötigen die __block, das ist ein anderes Thema. Wenn Sie nicht über __block verfügen, erfasst die Blockinstanz tatsächlich den vorherigen Wert der Variablen. Denken Sie daran, dass bei der Erstellung eines Blocks alle erfassten Variablen, die nicht mit __block gekennzeichnet sind, tatsächlich als const Kopie ihres Status an dem Punkt gespeichert werden, an dem der Block instanziiert wird. Und da der Block vor erstellt wird, ist er der Variablen zugewiesen, dh er erfasst den Zustand der capturedEvaluteAndRecurse Variable vor der Zuweisung, die nil (unter ARC; andernfalls ist es Müllspeicher) sein wird.

Im Wesentlichen können Sie sich eine gegebene Blockinstanz vorstellen, die tatsächlich eine Instanz einer versteckten Klasse ist, die für jede erfasste Variable einen ivar hat. Also mit Ihrem Code, würde der Compiler behandelt im Grunde ist es so etwas wie:

// Note: this isn't an accurate portrayal of what actually happens 
PrivateBlockSubclass *block = ^NSArray*(UIView *view){ ... }; 
block->stop = stop; 
block->evaluteAndRecurse = evaluateAndRecurse; 
evaluteAndRecurse = block; 

Hoffentlich macht klar, warum es den vorherigen Wert von evaluateAndRecurse anstelle des aktuellen Wertes erfaßt.

+0

Warum bleibt der ARC-Zähler nach der Zuweisung von evaluateAndRecurse auf Null? –

+0

@RamyAlZuhouri: Zählung behalten was? –

+0

Von dem Block, da der Block __unsafe_unready ist, sollte nach der Zuweisung nicht freigegeben werden? –

0

Ich habe etwas ähnliches getan, aber in einer anderen Art und Weise, um die Zeit zu reduzieren neue Arrays zuweisen, und hatte keine Probleme. Sie können versuchen, Ihre Methode so anzupassen, dass sie in etwa wie folgt aussieht: