11

Mam trudności z konwersją kodu NSOperation na ARC. Mój obiekt operacji używa bloku zakończenia, który z kolei zawiera blok GCD, który aktualizuje interfejs użytkownika w głównym wątku. Ponieważ odwołuję się do obiektu operacji z wewnątrz własnego bloku zakończenia, używam wskaźnika __weak, aby uniknąć wycieku pamięci. Jednak wskaźnik jest już ustawiony na zero do czasu uruchomienia mojego kodu.Odwoływanie się do obiektu NSOperation w jego własnym bloku ukończenia z ARC

Zawęziłem to do tej próbki kodu. Czy ktoś wie, gdzie popełniłem błąd i jak to zrobić?

NSOperationSubclass *operation = [[NSOperationSubclass alloc] init]; 
__weak NSOperationSubclass *weakOperation = operation; 

[operation setCompletionBlock:^{ 
    dispatch_async(dispatch_get_main_queue(), ^{ 

     // fails the check 
     NSAssert(weakOperation != nil, @"pointer is nil"); 

     ... 
    }); 
}]; 
+1

Cóż, chodzi o to, że słaby wskaźnik nie jest właścicielem. Jeśli nie ma nic innego, co trzymałoby zmienną (a jej nie ma), zostanie oczyszczone. Czy jesteś pewien, że dostaniesz wyciek, jeśli użyjesz 'operation'? Wygląda na to, że powinien zniknąć, gdy zostanie ukończony blok ukończenia, który powinien nastąpić, gdy tylko zostanie wywołany. (To może być naiwne.) –

+0

ARC skarżyło się na to podczas kompilacji. Bez tego używałbym bezpośrednio wskaźnika operacji (i nie wierzę, że przeciekałem pamięć). –

+1

Powodzenia z tym. Myślę, że walczyłem z tym przez kilka godzin, zanim poddałem się i zrobiłem coś innego. Ale minęło trochę czasu. :) –

Odpowiedz

10

nie jestem pewien na ten temat, ale poprawny sposób to zrobić jest ewentualnie dodać __block do zmiennej w pytaniu, a następnie ustawić go do zera na końcu bloku, aby upewnić się, że jest to wydany. See this question.

Nowy kod będzie wyglądać następująco:

NSOperationSubclass *operation = [[NSOperationSubclass alloc] init]; 
__block NSOperationSubclass *weakOperation = operation; 

[operation setCompletionBlock:^{ 
    dispatch_async(dispatch_get_main_queue(), ^{ 

     // fails the check 
     NSAssert(weakOperation != nil, @"pointer is nil"); 

     ... 
     weakOperation = nil; 
    }); 

}]; 
+3

Masz rację, wierzę. Dzięki! –

14

Innym rozwiązaniem byłoby:

NSOperationSubclass *operation = [[NSOperationSubclass alloc] init]; 
__weak NSOperationSubclass *weakOperation = operation; 

[operation setCompletionBlock:^{ 
    NSOperationSubclass *strongOperation = weakOperation; 

    dispatch_async(dispatch_get_main_queue(), ^{ 
     assert(strongOperation != nil); 
     ... 
    }); 
}]; 

[operationQueue addOperation:operation]; 

Zakładam również dodać obiekt do pracy NSOperationQueue. W takim przypadku kolejka zatrzymuje operację. Prawdopodobnie zachowuje ją również podczas wykonywania bloku zakończenia (chociaż nie znalazłem oficjalnego potwierdzenia bloku ukończenia).

Ale wewnątrz bloku zakończenia jest tworzony inny blok. Blok ten zostanie uruchomiony w pewnym momencie później, prawdopodobnie po zakończeniu bloku zakończenia NSOperations. Gdy tak się stanie, operation zostanie zwolniony przez kolejkę, a weakOperation będzie nil. Ale jeśli utworzymy kolejne silne odniesienie do tego samego obiektu z bloku zakończenia operacji, upewnimy się, że operation będzie istniał po uruchomieniu drugiego bloku i unikniemy cyklu zatrzymania, ponieważ nie przechwycimy zmiennej operation przez blok.

Firma Apple podaje ten przykład w dokumencie Transitioning to ARC Release Notes, zobacz ostatni fragment kodu w dokumencie Korzystanie z kodów Lifetime Qualifiers w celu uniknięcia tworzenia silnych cykli odwołań.

+4

+1 To jest poprawna odpowiedź.'NSOperation' zachowuje blok zakończenia, więc można bezpiecznie użyć słabego odniesienia do niego w bloku kończącym, ponieważ gwarantuje to, że wciąż żyje. Jednak problem OP polega na tym, że używają go w drugim bloku, który jest wykonywany później, a słaby punkt odniesienia nie jest tam gwarantowany. Prawidłowym rozwiązaniem jest, aby drugi blok miał silne odniesienie do 'NSOperation' – user102008

4

Przyjęta odpowiedź jest prawidłowa. Jednak nie ma potrzeby weakify operację jak iOS 8/Mac OS 10.10:

cytat z NSOperation documentation on @completionBlock:

w iOS 8 i nowsze i OS X v10.10 i później, ta właściwość jest ustawiona do zera po rozpoczęciu wykonywania bloku zakończenia.

Zobacz także this tweet od Pete Steinberger.

Powiązane problemy