Używam bloków od jakiegoś czasu, ale wydaje mi się, że są rzeczy, których mi brakuje w zarządzaniu pamięcią zarówno w środowiskach ARC, jak i innych środowiskach. Czuję, że głębsze zrozumienie spowoduje, że stracę wiele wycieków pamięci.Zarządzanie pamięcią C z blokami, ARC i nie-ARC
AFNetworking jest moim głównym zastosowaniem bloków w konkretnej aplikacji. W większości przypadków, w ramach obsługi zakończeń operacji, robię coś w stylu "[self.myArray addObject]".
W obu środowiskach włączonych ARC i nie-ARC, "self" zostanie zachowane zgodnie z this article from Apple.
Oznacza to, że za każdym razem, gdy wywoływany jest blok zakończenia operacji sieci AFNet, self jest zatrzymywane wewnątrz tego bloku i zwalniane, gdy blok ten wykracza poza zakres. Uważam, że dotyczy to zarówno ARC, jak i nie ARC. Uruchomiłem zarówno narzędzie Leaks, jak i Static Analyzer, aby znaleźć ewentualne wycieki pamięci. Żaden nie pokazał żadnego.
Jednak dopiero niedawno natknąłem się na ostrzeżenie, którego nie mogłem wymyślić. Używam ARC w tym konkretnym przykładzie.
Mam dwie zmienne instancji, które wskazują na zakończenie i niepowodzenie operacji sieciowej
@property (nonatomic, readwrite, copy) SFCompletionBlock completionBlock;
@property (nonatomic, readwrite, copy) SFFailureBlock failureBlock;
@synthesize failureBlock = _failureBlock;
@synthesize operation = _operation;
gdzieś w kodzie, to zrobić:
[self.operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id
responseObject) {
NSError *error = [NSError errorWithDomain:@"com.test" code:100 userInfo:@{@"description": @"zero results"}];
_failureBlock(error);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"nothing");
}];
Xcode narzeka na linii, która wywołuje failureBlock, z komunikatem "Capturing" self "silnie w tym bloku prawdopodobnie spowoduje cykl zatrzymania.Myślę, że Xcode ma rację: blok awarii zachowuje siebie, a ja trzyma własną kopię bloku, więc żaden z dwóch zostanie zwrócony.
Mam jednak następujące pytania/uwagi.
1) Jeśli zmienię _failureBlock (błąd) na "self.failureBlock (error)" (bez cudzysłowów) kompilator przestaje narzekać. Dlaczego? Czy jest to przeciek pamięci kompilatora brakuje?
2) Ogólnie, jaka jest najlepsza praktyka do pracy z blokami w środowiskach z włączoną obsługą ARC i bez ARC podczas korzystania z bloków , które są zmiennymi instancji? Wydaje się, że w przypadku bloków ukończenia i awarii w AFNetworking, te dwa bloki są zmiennymi instancji , a więc prawdopodobnie nie należą do kategorii cykli podtrzymania, które opisałem powyżej. Ale kiedy używasz bloków postępu w AFNetworking, co można zrobić, aby uniknąć zachowywania cykli, jak ten powyżej?
Chciałbym usłyszeć myśli innych ludzi na temat ARC i nie-ARC za pomocą bloków i problemów/rozwiązań z zarządzaniem pamięcią. Uważam, że te sytuacje są podatne na błędy i uważam, że konieczna jest dyskusja na ten temat.
Nie wiem, czy to ma znaczenie, ale używam Xcode 4.4 z najnowszą wersją LLVM.
Dziękuję za tę odpowiedź. to jest dużo jedzenia do przemyślenia. Na marginesie, to, co powiedziałeś o wywołaniu zwrotnym i wątku tła powodującym awarie, jest poprawne. Jednak wszystkie bloki ukończenia są wywoływane w głównym wątku w moim przypadku, aby uniknąć takich problemów.Jeśli chodzi o inne rzeczy, rozwiązanie "return the block from a method" do zachowania cykli nie rozwiązuje podstawowego problemu tworzenia komponentu wielokrotnego użytku, w którym dzwoniący decyduje o zakończeniu bloku. – csotiriou
Jeśli ujawniasz interfejs API z blokowymi oddzwanianiem, nie możesz uniemożliwić wywołującym tworzenia własnych cykli przechowywania. Twój interfejs API powinien zadbać o zwolnienie bloków wywołań zwrotnych zaraz po ich wykonaniu i prawdopodobnie powinien ujawnić metodę anulowania, która spowoduje również zwolnienie bloków wywołań zwrotnych. W praktyce większość rozmówców implementuje wywołania zwrotne w każdym razie, a nie we właściwościach. W takim przypadku semantyka pamięci jest taka sama, jak zwrot bloku z metody - cykl zatrzymywania rozpoczyna się, gdy wywołują twoje API i kończy się, gdy twój interfejs API zwalnia blok wywołania zwrotnego. –