2012-06-12 13 views
6

Z Transitioning to ARC Release NotesDlaczego musimy ustawić zmienną __block na zero?

zażycia kwalifikacyjnych, których należy unikać Silne cykli odniesienia

Można użyć kwalifikatorów całe życie unikać silnych cykli odniesienia. Na przykład , zazwyczaj jeśli masz wykres obiektów ułożonych w hierarchii nadrzędnego podrzędnego dziecka, a rodzice muszą odnosić się do swoich dzieci i na odwrót, wtedy relacja między rodzicem a dzieckiem jest silna, a rodzic - dziecko - silnym i -parent relacja słaba. Inne sytuacje mogą być bardziej subtelne, szczególnie gdy dotyczą obiektów blokowych.

W ręcznym trybie liczenia odniesień, __block id x; ma efekt polegający na tym, że nie zachowuje wartości . W trybie ARC domyślnie __block id x; zachowuje wartość x (tylko , podobnie jak wszystkie inne wartości). Aby uzyskać ręczny tryb liczenia odniesień zachowanie w ramach ARC, można użyć __unsafe_unretained __block id x;. Jak wskazuje nazwa __unsafe_unretained, posiadanie niepobranej zmiennej jest niebezpieczne (ponieważ może zwisać) i dlatego jest zniechęcane. Dwiema lepszymi opcjami są: użycie __weak (jeśli nie potrzebujesz obsługi iOS 4 lub OS X v10.6), lub ustaw wartość __block na nil, aby przerwać cykl zachowywania.

Okay, więc co różni się od zmiennej __block?

Dlaczego ustawić na nil tutaj? Czy zmienna __block zachowała się dwukrotnie? Kto ma wszystkie odniesienia? Blok? Kupa? Stos? Groźba? Co?

Poniższy fragment kodu ilustruje ten problem za pomocą wzoru, który jest czasem używany w ręcznym liczeniu odwołań.

MyViewController *myController = [[MyViewController alloc] init…]; 

// ... 

myController.completionHandler = ^(NSInteger result) { 
    [myController dismissViewControllerAnimated:YES completion:nil]; 
}; 

[self presentViewController:myController animated:YES completion:^{ 
    [myController release]; 
}]; 

Jak opisano, zamiast tego można użyć __block kwalifikator i ustawić zmienną MyController do nil w obsługi realizacji:

MyViewController * __block myController = [[MyViewController alloc] init…]; //Why use __block. my controller is not changed at all 

// ... 

myController.completionHandler = ^(NSInteger result) { 
    [myController dismissViewControllerAnimated:YES completion:nil]; 

    myController = nil; //Why set to nil here? Is __block variable retained twice? Who hold all the reference? The block? The heap? The stack? The thread? The what? 
}; 

także dlaczego myController nie jest ustawiony na nil przez kompilator. Dlaczego musimy to robić? Wygląda na to, że kompilator wie, kiedy myController nie będzie już używany, a mianowicie, gdy blok wygaśnie.

Odpowiedz

14

Kiedy masz kod tego formularza:

object.block = ^{ 
    // reference object from inside the block 
    [object someMethodOrProperty]; 
}; 

object zachowa lub skopiować blok dajesz do niego. Ale sam blok zachowa także object, ponieważ jest silnie odwołany z wewnątrz bloku. To jest cykl zatrzymania. Nawet po zakończeniu bloku cykl odniesienia nadal istnieje i nie można zwolnić ani obiektu, ani bloku. Pamiętaj, że blok może być wywołany wiele razy, więc nie może po prostu zapomnieć o wszystkich zmiennych, do których się odwołuje po tym, jak zakończyło wykonywanie raz.

Aby przerwać ten cykl, można zdefiniować object jako zmienną __block, która umożliwia zmianę jej wartości z wnętrza bloku, np.zmieniając go do nil przerwać cykl:

__block id object = ...; 
object.block = ^{ 
    // reference object from inside the block 
    [object someMethodOrProperty]; 

    object = nil; 
    // At this point, the block no longer retains object, so the cycle is broken 
}; 

Kiedy przypisać object do nil na końcu bloku, blok nie będzie dłużej zachowują object i cykl zachowanie jest zepsuty. Dzięki temu oba obiekty mogą zostać zwolnione.

Jednym z konkretnych przykładów jest właściwość NSOperation o numerze completionBlock. Jeśli używasz completionBlock dostęp do wyników operacji jest, trzeba złamać zachować cykl, który jest tworzony:

__block NSOperation *op = [self operationForProcessingSomeData]; 
op.completionBlock = ^{ 
    // since we strongly reference op here, a retain cycle is created 
    [self operationFinishedWithData:op.processedData]; 

    // break the retain cycle! 
    op = nil; 
} 

Jako dokumentacja opisuje, istnieje szereg innych technik można również użyć, aby złamać te zachowują cykli . Na przykład będziesz musiał użyć innej techniki w kodzie innym niż ARC, niż w kodzie ARC.

+0

"Ale sam blok również zachowa obiekt, ponieważ jest silnie odwołany z wewnątrz bloku." Czemu? Zamknięcie. –

+0

W jaki sposób dodanie __block powoduje jakąkolwiek różnicę? –

+0

Gdy blok przechwytuje wskaźnik do obiektu c, obiekt ten będzie zachowany, chyba że użyjesz '__weak' lub' __unsafe_unretained' (lub '__block' w kodzie innym niż ARC). –

0

Wolę to rozwiązanie

typeof(self) __weak weakSelf = self; 
self.rotationBlock = ^{ 
    typeof (weakSelf) __strong self = weakSelf; 

    [self yourCodeThatReferenceSelf]; 
}; 

co się dzieje, że blok będzie uchwycić siebie jako słabego odniesienia i nie będzie zachować cykl. self wewnątrz bloku jest przedefiniowany jako __strong self = weakSelf przed uruchomieniem twojego kodu. Zapobiega to zwolnieniu ja podczas pracy bloku.

Powiązane problemy