Naprawiono ostrzeżenie „Mocne przechwycenie [obiektu] w tym bloku prawdopodobnie doprowadzi do cyklu przechowywania” w kodzie z włączoną funkcją ARC

141

W kodzie z włączoną funkcją ARC, jak naprawić ostrzeżenie o potencjalnym cyklu przechowywania podczas korzystania z interfejsu API opartego na blokach?

Ostrzeżenie:
Capturing 'request' strongly in this block is likely to lead to a retain cycle

utworzony przez ten fragment kodu:

ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:request.rawResponseData error:nil];
    // ...
    }];

Ostrzeżenie jest powiązane z użyciem obiektu requestwewnątrz bloku.

Guillaume
źródło
1
Prawdopodobnie powinieneś używać responseDatazamiast rawResponseData, sprawdź dokumentację ASIHTTPRequest.
0xcewano

Odpowiedzi:

165

Odpowiadam sobie:

Z mojego rozumienia dokumentacji wynika, że ​​użycie słowa kluczowego blocki ustawienie zmiennej na zero po użyciu go w bloku powinno być w porządku, ale nadal wyświetla ostrzeżenie.

__block ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:request.responseData error:nil];
    request = nil;
// ....

    }];

Aktualizacja: sprawiłem, że działa ze słowem kluczowym „_ słaby” zamiast „ _block” i używając zmiennej tymczasowej:

ASIHTTPRequest *_request = [[ASIHTTPRequest alloc] initWithURL:...
__weak ASIHTTPRequest *request = _request;

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:request.responseData error:nil];
    // ...
    }];

Jeśli chcesz również kierować reklamy na iOS 4, użyj __unsafe_unretainedzamiast __weak. To samo zachowanie, ale wskaźnik pozostaje zawieszony zamiast być automatycznie ustawiany na zero, gdy obiekt zostanie zniszczony.

Guillaume
źródło
8
Na podstawie dokumentacji ARC wygląda na to, że musisz użyć __unsafe_unretained __block razem, aby uzyskać takie samo zachowanie jak wcześniej podczas korzystania z ARC i bloków.
Hunter,
4
@SeanClarkHess: Kiedy łączę dwie pierwsze linie, otrzymuję ostrzeżenie: „Przypisywanie zachowanego obiektu do słabej zmiennej; obiekt zostanie zwolniony po przypisaniu”
Guillaume
1
@ Guillaume dzięki za odpowiedź, trochę jak przeoczyłem zmienną tymczasową, spróbowałem i ostrzeżenia zniknęły. Czy wiesz, dlaczego to działa? Czy to tylko oszukanie kompilatora, aby powstrzymać ostrzeżenia, czy też ostrzeżenie jest już nieaktualne?
Chris Wagner,
2
Wysłałem
3
Czy ktoś może wyjaśnić, dlaczego potrzebujesz słów kluczowych __block i __weak? Wydaje mi się, że powstaje cykl retainy, ale go nie widzę. W jaki sposób utworzenie zmiennej tymczasowej rozwiązuje problem?
user798719
50

Ten problem występuje, ponieważ przypisujesz blok do żądania, który ma silne odniesienie do żądania. Blok automatycznie zachowa żądanie, więc pierwotne żądanie nie zostanie zwolnione z powodu cyklu. Ma sens?

To po prostu dziwne, ponieważ tagujesz obiekt żądania za pomocą __block, aby mógł odwoływać się do siebie. Możesz to naprawić, tworząc obok niego słabe odniesienie .

ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...];
__weak ASIHTTPRequest *wrequest = request;

[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:wrequest.rawResponseData error:nil];
    // ...
    }];
ZaBlanc
źródło
__weak ASIHTTPRequest * wrequest = request; nie działa dla mnie. Podając błąd użyłem __block ASIHTTPRequest * blockRequest = request;
Ram G.
13

Powoduje to poprzez zatrzymanie siebie w bloku. Blok będzie dostępny od siebie, a do siebie odnosi się blok. stworzy to cykl utrzymania.

Spróbuj rozwiązać ten problem, tworząc słabe odniesienie do self

__weak typeof(self) weakSelf = self;

operationManager = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operationManager.responseSerializer = [AFJSONResponseSerializer serializer];
[operationManager setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {

    [weakSelf requestFinishWithSucessResponseObject:responseObject withAFHTTPRequestOperation:operation andRequestType:eRequestType];

} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    [weakSelf requestFinishWithFailureResponseObject:error withAFHTTPRequestOperation:operation andRequestType:eRequestType];
}];
[operationManager start];
HDdeveloper
źródło
To jest poprawna odpowiedź i należy ją tak zapisać
Benjamin,
6

Czasami kompilator xcode ma problemy z identyfikatorem zachowań cykli, więc jeśli jesteś pewien, że nie zachowujesz kompletnego elementu, możesz umieścić flagę kompilatora w następujący sposób:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"

-(void)someMethod {
}
GOrozco58
źródło
1
Niektórzy mogą twierdzić, że to zły projekt, ale czasami tworzę niezależne obiekty, które wiszą w pamięci, dopóki nie zostaną ukończone z zadaniem asynchronicznym. Są one zachowywane przez właściwość CompleteBlock, która zawiera silne odniesienie do siebie, tworząc celowy cykl przechowywania. CompletionBlock zawiera self.completionBlock = nil, co zwalnia komendę CompleteBlock i przerywa cykl przechowywania, umożliwiając zwolnienie obiektu z pamięci po zakończeniu zadania. Twoja odpowiedź jest przydatna, aby pomóc wyciszyć ostrzeżenia, które pojawiają się, gdy to robię.
hiperspazm
1
szczerze mówiąc, szanse na to, że ktoś ma rację, a kompilator się myli, są bardzo małe. Więc powiedziałbym, że przekroczenie ostrzeżeń jest ryzykownym biznesem
Max MacLeod
3

Kiedy wypróbowuję rozwiązanie dostarczone przez Guillaume, wszystko jest w porządku w trybie debugowania, ale ulega awarii w trybie wydania.

Zauważ, że nie używaj __weak, ale __unsafe_unretained, ponieważ moim celem jest iOS 4.3.

Mój kod ulega awarii, gdy setCompletionBlock: jest wywoływana na obiekcie „request”: żądanie zostało cofnięte ...

Tak więc to rozwiązanie działa zarówno w trybie debugowania, jak i wydania:

// Avoiding retain cycle :
// - ASIHttpRequest object is a strong property (crashs if local variable)
// - use of an __unsafe_unretained pointer towards self inside block code

self.request = [ASIHttpRequest initWithURL:...
__unsafe_unretained DataModel * dataModel = self;

[self.request setCompletionBlock:^
{
    [dataModel processResponseWithData:dataModel.request.receivedData];        
}];
szkwał2022
źródło
Ciekawe rozwiązanie. Czy wiesz, dlaczego ulega awarii w trybie wydania, a nie w debugowaniu?
Valerio Santinelli
2
ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:...
__block ASIHTTPRequest *blockRequest = request;
[request setCompletionBlock:^{
    NSDictionary *jsonDictionary = [[CJSONDeserializer deserializer] deserialize:blockRequest.responseData error:nil];
    blockRequest = nil;
// ....

}];

jaka jest różnica między odwołaniem __weak a __block?

Emil Marashliev
źródło