6

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.

Odpowiedz

6

1) w przypadku zmiany _failureBlock (błąd) na "self.failureBlock (błąd)" (bez cudzysłowów) kompilator zatrzymuje sędzią. Dlaczego? Czy to wyciek pamięci kompilatorowi brakuje?

Cykl zatrzymania istnieje w obu przypadkach. Jeśli kierujesz iOS 5+, można przekazać w słabym odniesieniu do siebie:

__weak MyClass *weakSelf; 
[self.operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { 
    NSError *error = [NSError errorWithDomain:@"com.test" code:100 userInfo:@{@"description": @"zero results"}]; 
    if (weakSelf.failureBlock) weakSelf.failureBlock(error); 
} failure:^(AFHTTPRequestOperation *operation, NSError *error) { 
    NSLog(@"nothing"); 
}]; 

Teraz samo nie zostaną zachowane, a jeśli jest zwalniane przed zwrotna jest wywoływana, callback jest no- op. Jednak może się zdarzyć, że podczas wywołania zwrotnego wywoływana będzie wątek w tle, więc ten wzorzec może powodować sporadyczne awarie.

2) W ogóle, co to jest najlepsze praktyki do pracy z bloków w obu ARC i non-ARC włączone środowisk przy użyciu bloków, które są instancji zmienne? Wydaje się, że w przypadku ukończenia i awarii bloków w AFNetworking te dwa bloki nie są zmiennymi instancji, więc prawdopodobnie nie należą do kategorii cykli zatrzymania opisanych powyżej. Ale kiedy używasz bloków postępu w AFNetworking, co można zrobić, aby uniknąć cykli zachowywania, takich jak ten powyżej?

Przez większość czasu myślę, że it's better not to store blocks in instance variables. Jeśli zamiast tego zwrócisz blok z metody w swojej klasie, nadal będziesz mieć cykl zatrzymywania, ale będzie on istniał tylko od czasu wywołania metody do czasu zwolnienia bloku. Zapobiega to dezalokacji instancji podczas wykonywania bloku, ale cykl zatrzymania kończy się po zwolnieniu bloku:

-(SFCompletionBlock)completionBlock { 
    return ^(AFHTTPRequestOperation *operation , id responseObject) { 
     [self doSomethingWithOperation:operation]; 
    }; 
} 

[self.operation setCompletionBlockWithSuccess:[self completionBlock] 
             failure:[self failureBlock] 
]; 
+0

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

+0

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. –

2

Oznacza to, że gdy blok Zakończenie sieci AFNetworking operacja nazywa, self jest zatrzymywana wewnątrz tego bloku, a wydany kiedy ten blok wykracza poza zakres.

Nie, self zostaje zatrzymany przez blok podczas tworzenia bloku.I jest zwalniane, gdy blok zostaje zwolniony.

wierzę Xcode ma rację: blok awaria utrzymuje siebie i własny posiada własną kopię bloku, więc żaden z nich będzie zwalniane.

Blok, o którym wiadomo, że zachowuje self jest blokiem ukończenia przekazanym do setCompletionBlockWithSuccess. self nie zawiera odniesienia do tego bloku. Raczej, self.operation (prawdopodobnie w pewnym rodzaju NSOperation) zachowuje bloki podczas wykonywania. Tak więc jest czasowo cykl. Jednak po wykonaniu operacji cykl zostanie zerwany.

1) w przypadku zmiany _failureBlock (błąd) na "self.failureBlock (błąd)" (bez cudzysłowów) kompilator zatrzymuje sędzią. Dlaczego? Czy to wyciek pamięci kompilatorowi brakuje?

Nie powinno być żadnych różnic. self jest przechwytywany w obu przypadkach. Kompilator nie ma możliwości przechwycenia wszystkich przypadków zatrzymania cykli.