Zawsze przekazujesz słabe odniesienie do siebie do bloku w ARC?

252

Jestem trochę zdezorientowany co do użycia bloku w Objective-C. Obecnie używam ARC i mam dość dużo bloków w mojej aplikacji, obecnie zawsze odnoszących się do selfzamiast jej słabego odniesienia. Czy może to być przyczyną zatrzymania selfi zablokowania bloków przed zwolnieniem? Pytanie brzmi: czy zawsze powinienem używać weakodniesienia selfw bloku?

-(void)handleNewerData:(NSArray *)arr
{
    ProcessOperation *operation =
    [[ProcessOperation alloc] initWithDataToProcess:arr
                                         completion:^(NSMutableArray *rows) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self updateFeed:arr rows:rows];
        });
    }];
    [dataProcessQueue addOperation:operation];
}

ProcessOperation.h

@interface ProcessOperation : NSOperation
{
    NSMutableArray *dataArr;
    NSMutableArray *rowHeightsArr;
    void (^callback)(NSMutableArray *rows);
}

ProcessOperation.m

-(id)initWithDataToProcess:(NSArray *)data completion:(void (^)(NSMutableArray *rows))cb{

    if(self =[super init]){
        dataArr = [NSMutableArray arrayWithArray:data];
        rowHeightsArr = [NSMutableArray new];
        callback = cb;
    }
    return self;
}

- (void)main {
    @autoreleasepool {
        ...
        callback(rowHeightsArr);
    }
}
the_critic
źródło
Jeśli chcesz pogłębionego dyskursu na ten temat, przeczytaj dhoerl.wordpress.com/2013/04/23/…
David H

Odpowiedzi:

721

Pomaga nie koncentrować się na dyskusji strongani jej weakczęści. Zamiast tego skup się na części cyklu .

Cykl zatrzymania to pętla, która zachodzi, gdy Obiekt A zachowuje Obiekt B, a Obiekt B zachowuje Obiekt A. W takiej sytuacji, jeśli dowolny obiekt zostanie zwolniony:

  • Obiekt A nie zostanie zwolniony, ponieważ Obiekt B zawiera odniesienie do niego.
  • Ale Obiekt B nigdy nie zostanie zwolniony, dopóki Obiekt A ma do niego odniesienie.
  • Ale Obiekt A nigdy nie zostanie zwolniony, ponieważ Obiekt B zawiera odniesienie do niego.
  • ad infinitum

Tak więc te dwa obiekty będą po prostu wisiały w pamięci przez cały czas trwania programu, mimo że powinny, jeśli wszystko działałoby poprawnie, zostać zwolnione.

Dlatego martwimy się o zachowanie cykli , a same bloki nie mają nic wspólnego z tymi cyklami. To nie jest problem, na przykład:

[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
   [self doSomethingWithObject:obj];
}];

Blok zachowuje self, ale selfnie zachowuje bloku. Jeśli jedno lub drugie zostanie zwolnione, cykl nie zostanie utworzony i wszystko zostanie zwolnione tak, jak powinno.

W kłopoty jest coś takiego:

//In the interface:
@property (strong) void(^myBlock)(id obj, NSUInteger idx, BOOL *stop);

//In the implementation:
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [self doSomethingWithObj:obj];     
}];

Teraz twoja selffunkcja ( ) ma jawne strongodniesienie do bloku. I blok ma ukryte silne odniesienie do self. To cykl, a teraz żaden obiekt nie zostanie odpowiednio zwolniony.

Ponieważ w takiej sytuacji self z definicji istnieje już strongodwołanie do bloku, zwykle najłatwiej go rozwiązać, tworząc wyraźnie słabe odwołanie do selfbloku:

__weak MyObject *weakSelf = self;
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [weakSelf doSomethingWithObj:obj];     
}];

Ale nie powinien to być domyślny wzorzec, który stosujesz w przypadku bloków, które wywołują self! Powinno to być użyte tylko do przerwania tego, co w innym przypadku byłoby cyklem zatrzymania między sobą a blokiem. Jeśli wszędzie zastosujesz ten wzorzec, ryzykujesz przekazaniem bloku do czegoś, co zostało wykonane po selfzwolnieniu.

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not retained!
  [weakSelf doSomething];
}];
jemmons
źródło
2
Nie jestem pewien, czy A zatrzyma B, B zatrzyma A wykona nieskończony cykl. Z punktu widzenia liczby referencyjnej liczba referencyjna A i B wynosi 1. Przyczyną cyklu zatrzymania dla tej sytuacji jest brak innej grupy silnych referencji A i B na zewnątrz - oznacza to, że nie możemy osiągnąć tych dwóch obiektów (my nie mogą kontrolować A, aby uwolnić B i odwrotnie), dlatego A i B odnoszą się do siebie nawzajem, aby utrzymać się przy życiu.
Danyun Liu
@Danyun Chociaż prawdą jest, że cykl zatrzymania między A i B nie jest niemożliwy do odzyskania, dopóki wszystkie inne odniesienia do tych obiektów nie zostaną opublikowane, nie oznacza to, że jest to mniejszy cykl. I odwrotnie, tylko dlatego, że określony cykl można odzyskać, nie oznacza, że ​​można go mieć w kodzie. Cykle zatrzymujące to zapach złego projektu.
jemmons,
@jemmons Tak, zawsze powinniśmy unikać projektu cyklu zachowania tak bardzo, jak to możliwe.
Danyun Liu,
1
@Master Nie mogę powiedzieć. Zależy to całkowicie od wdrożenia twojej -setCompleteionBlockWithSuccess:failure:metody. Ale jeśli paginatorjest własnością ViewController, a te bloki nie zostaną wywołane po ViewController, zostaną zwolnione, użycie __weakodniesienia byłoby bezpiecznym ruchem (ponieważ selfjest właścicielem rzeczy, która jest właścicielem bloków, a więc prawdopodobnie nadal będzie w pobliżu, gdy wywołają je bloki nawet jeśli tego nie zachowują). Ale to dużo „jeśli”. To naprawdę zależy od tego, co to ma zrobić.
jemmons
1
@Jai Nie, i to jest sedno problemu zarządzania pamięcią w przypadku bloków / zamknięć. Przedmioty zostają zwolnione, gdy nic nie jest ich właścicielem. MyObjecti SomeOtherObjectoboje są właścicielami bloku. Ale ponieważ blok jest odesłanie do MyObjectto weakblok nie własny MyObject. Więc gdy blok jest gwarantowana istnieć tak długo, jak albo MyObject czy SomeOtherObject istnieje, nie ma gwarancji, że MyObjectbędzie istnieć tak długo, jak blok robi. MyObjectmożna całkowicie zwolnić i tak długo, jak długo SomeOtherObjectistnieje, blok będzie nadal istniał.
jemmons,
26

Nie zawsze musisz używać słabego odniesienia. Jeśli twój blok nie zostanie zachowany, ale zostanie wykonany, a następnie odrzucony, możesz mocno przechwycić siebie, ponieważ nie utworzy to cyklu zatrzymania. W niektórych przypadkach chcesz nawet, aby blok utrzymywał siebie do momentu ukończenia bloku, aby nie przedwcześnie zwolnił. Jeśli jednak silnie przechwycisz blok, a wewnątrz siebie przechwytywanie utworzy cykl zatrzymania.

Leo Natan
źródło
Cóż, po prostu wykonuję blok jako wywołanie zwrotne i nie chciałbym, aby ja w ogóle został zwolniony. Ale wygląda na to, że tworzyłem cykle zatrzymania, ponieważ kontroler widoku, o którym mowa, nie został zwolniony ...
the_critic
1
Na przykład, jeśli masz element przycisku paska, który jest zatrzymywany przez kontroler widoku, i silnie przechwytujesz siebie w tym bloku, nastąpi cykl przechowywania.
Leo Natan,
1
@MartinE. Powinieneś po prostu zaktualizować swoje pytanie za pomocą próbek kodu. Lew ma rację (+1), że niekoniecznie powoduje silny cykl odniesienia, ale może, w zależności od sposobu użycia tych bloków. Łatwiej będzie nam pomóc, jeśli udostępnisz fragmenty kodu.
Rob
@LeoNatan Rozumiem pojęcie zachowania cykli, ale nie jestem całkiem pewien, co dzieje się w blokach, więc to mnie trochę
dezorientuje
W powyższym kodzie instancja użytkownika zostanie zwolniona po zakończeniu operacji i zwolnieniu bloku. Powinieneś przeczytać o tym, jak działają bloki i co i kiedy wychwytują w swoim zakresie.
Leo Natan
26

Całkowicie zgadzam się z @jemmons:

Ale nie powinien to być domyślny wzorzec, który stosujesz w przypadku bloków nazywających się self! Powinno to być użyte tylko do przerwania tego, co w innym przypadku byłoby cyklem zatrzymania między sobą a blokiem. Jeśli wszędzie zastosujesz ten wzorzec, ryzykujesz przekazaniem bloku do czegoś, co zostało wykonane po zwolnieniu samego siebie.

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not  retained!
  [weakSelf doSomething];
}];

Aby rozwiązać ten problem, można zdefiniować silne odniesienie weakSelfwewnątrz bloku:

__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  MyObject *strongSelf = weakSelf;
  [strongSelf doSomething];
}];
Ilker Baltaci
źródło
3
Czy strongSelf nie zwiększyłby liczby referencji dla poorSelf? W ten sposób tworzysz cykl przechowywania?
mskw
12
Zachowaj cykle tylko ważne, jeśli istnieją w stanie statycznym obiektu. Podczas gdy kod jest wykonywany, a jego stan jest zmienny, wielokrotne i prawdopodobnie zbędne zatrzymania są w porządku. W każdym razie, jeśli chodzi o ten wzorzec, przechwytywanie silnego odniesienia nie ma nic wspólnego z przypadkiem samowolnego zwolnienia przed uruchomieniem bloku. Zapewnia to, że jaźń nie zostanie zwolniona podczas wykonywania bloku. Ma to znaczenie, jeśli blok sam wykonuje operacje asynchroniczne, co daje okno, aby tak się stało.
Pierre Houston
@smallduck, twoje wyjaśnienie jest świetne. Teraz rozumiem to lepiej. Dzięki, książki tego nie opisały.
Huibin Zhang
5
To nie jest dobry przykład strongSelf, ponieważ wyraźne dodanie strongSelf jest dokładnie tym, co zrobiłoby to środowisko wykonawcze: w wierszu doSomething pobierane jest silne odniesienie na czas trwania wywołania metody. Jeśli słabySelf został już unieważniony, silne odwołanie jest zerowe, a wywołanie metody nie działa. W przypadku, gdy strongSelf pomaga, jeśli masz serię operacji lub uzyskujesz dostęp do pola członka ( ->), w którym chcesz zagwarantować, że faktycznie masz ważne odwołanie i utrzymujesz je w sposób ciągły przez cały zestaw operacji, np.if ( strongSelf ) { /* several operations */ }
Ethan
19

Jak zauważa Leo, kod dodany do pytania nie sugerowałby silnego cyklu odniesienia (inaczej: zachowaj cykl). Jednym z problemów związanych z operacją, które mogłyby spowodować silny cykl odniesienia, byłby brak zwolnienia operacji. Chociaż fragment kodu sugeruje, że nie zdefiniowałeś operacji jako współbieżnej, ale jeśli tak, to nie zostanie zwolniony, jeśli nigdy nie opublikujesz wiadomości isFinished, nie będziesz mieć zależności cyklicznych lub czegoś podobnego. A jeśli operacja nie zostanie zwolniona, kontroler widoku również nie zostanie zwolniony. Sugerowałbym dodanie punktu przerwania lub metody NSLogoperacji dealloci potwierdzenie, że zostanie wywołany.

Powiedziałeś:

Rozumiem pojęcie zatrzymania cykli, ale nie jestem całkiem pewien, co dzieje się w blokach, więc trochę mnie to dezorientuje

Problemy z cyklem przechowywania (silny cykl odniesienia) występujące w blokach są podobne do problemów z cyklem przechowywania, które znasz. Blok zachowa silne odwołania do wszelkich obiektów pojawiających się w bloku i nie zwolni tych silnych odniesień, dopóki sam blok nie zostanie zwolniony. Tak więc, jeśli odwołania do bloku self, a nawet po prostu odwołują się do zmiennej instancji self, która zachowa silne odniesienie do siebie, to nie zostanie rozwiązane, dopóki blok nie zostanie zwolniony (lub w tym przypadku, dopóki nie NSOperationzostanie zwolniona podklasa).

Aby uzyskać więcej informacji, zobacz temat Unikanie silnych cykli odniesienia podczas przechwytywania części własnej programowania z dokumentem Cel-C: Praca z blokami .

Jeśli kontroler widoku nadal nie jest uwalniany, musisz po prostu określić, gdzie znajduje się nierozwiązane silne odwołanie (zakładając, że potwierdziłeś, że NSOperationjest on zwolniony). Typowym przykładem jest użycie powtarzania NSTimer. Lub jakiś niestandardowy delegatelub inny obiekt, który błędnie utrzymuje strongodniesienie. Często można używać przyrządów do śledzenia miejsc, w których obiekty uzyskują silne referencje, np .:

rekord odniesienia zlicza w Xcode 6

Lub w Xcode 5:

rekord odniesienia zlicza w Xcode 5

Obrabować
źródło
1
Innym przykładem może być zachowanie operacji w kreatorze bloku i nie zwolnienie jej po zakończeniu. +1 na miły napisz!
Leo Natan
@LeoNatan zgodził się, chociaż fragment kodu reprezentuje go jako zmienną lokalną, która zostałaby zwolniona, gdyby używał ARC. Ale masz całkowitą rację!
Rob
1
Tak, podałem tylko przykład, zgodnie z żądaniem PO w innej odpowiedzi.
Leo Natan,
Nawiasem mówiąc, Xcode 8 ma „Debug Memory Graph”, który jest jeszcze łatwiejszym sposobem na znalezienie silnych odniesień do obiektów, które nie zostały wydane. Zobacz stackoverflow.com/questions/30992338/… .
Rob
0

Niektóre wyjaśnienia ignorują warunek dotyczący cyklu przechowywania [Jeśli grupa obiektów jest połączona kręgiem silnych relacji, utrzymują się one przy życiu, nawet jeśli nie ma żadnych silnych referencji spoza grupy.] Aby uzyskać więcej informacji, przeczytaj dokument

Danyun Liu
źródło
-2

Oto jak możesz użyć self wewnątrz bloku:

// wywołanie bloku

 NSString *returnedText= checkIfOutsideMethodIsCalled(self);

NSString* (^checkIfOutsideMethodIsCalled)(*)=^NSString*(id obj)
{
             [obj MethodNameYouWantToCall]; // this is how it will call the object 
            return @"Called";


};
Ranjeet Singh
źródło