Zachowaj cykl na sobie z blokami

167

Obawiam się, że to pytanie jest dość proste, ale myślę, że jest istotne dla wielu programistów Objective-C, którzy wchodzą do bloków.

Słyszałem, że skoro bloki przechwytują zmienne lokalne, do których odwołują się w nich jako constkopie, użycie selfw bloku może skutkować cyklem zachowania, jeśli ten blok zostanie skopiowany. Więc powinniśmy użyć __blockdo wymuszenia bezpośredniego działania bloku selfzamiast kopiowania.

__block typeof(self) bself = self;
[someObject messageWithBlock:^{ [bself doSomething]; }];

zamiast po prostu

[someObject messageWithBlock:^{ [self doSomething]; }];

Chciałbym wiedzieć, co następuje: jeśli to prawda, czy istnieje sposób, żebym mógł uniknąć brzydoty (poza używaniem GC)?

Jonathan Sterling
źródło
3
Lubię dzwonić do moich selfproxy thistylko po to, żeby coś odwrócić. W JavaScript nazywam moje thisdomknięcia self, więc jest to przyjemne i zrównoważone. :)
devios1
Zastanawiam się, czy należy wykonać jakieś równoważne działanie, jeśli używam bloków Swift
Ben Lu,
@BenLu absolutnie! w zamknięciach Swift (i funkcji, które są przekazywane wokół, które wspominają o sobie w sposób pośredni lub jawny), zachowają siebie. Czasami jest to pożądane, a innym razem tworzy cykl (ponieważ samo zamknięcie staje się własnością jaźni (lub jest własnością czegoś, co jest jej własnością). Głównym powodem tego jest ARC.
Ethan
1
Aby uniknąć problemów, odpowiednim sposobem zdefiniowania „self” używanego w bloku jest „__typeof (self) __weak słabySelf = self;” by mieć słabe odniesienie.
XLE_22

Odpowiedzi:

169

Ściśle mówiąc, fakt, że jest to kopia const, nie ma nic wspólnego z tym problemem. Bloki zachowają wszelkie wartości obj-c przechwycone podczas ich tworzenia. Tak się składa, że ​​obejście problemu const-copy jest identyczne jak obejście problemu zachowania; mianowicie przy użyciu __blockklasy pamięci dla zmiennej.

W każdym razie, odpowiadając na twoje pytanie, nie ma tutaj prawdziwej alternatywy. Jeśli projektujesz własny interfejs API oparty na blokach i ma to sens, możesz poprosić o przekazanie bloku wartości selfin jako argumentu. Niestety nie ma to sensu w przypadku większości interfejsów API.

Należy pamiętać, że odwoływanie się do ivar ma dokładnie ten sam problem. Jeśli potrzebujesz odwołać się do ivar w swoim bloku, użyj zamiast tego właściwości lub użyj bself->ivar.


Dodatek: Podczas kompilacji jako ARC, __blocknie przerywa już zachowań cykli. Jeśli kompilujesz dla ARC, musisz użyć __weaklub __unsafe_unretainedzamiast tego.

Lily Ballard
źródło
Nie ma problemu! Byłbym wdzięczny, gdyby to odpowiadało na pytanie w sposób zadowalający, gdybyś mógł wybrać tę odpowiedź jako właściwą odpowiedź na swoje pytanie. Jeśli nie, daj mi znać, jak mogę lepiej odpowiedzieć na Twoje pytanie.
Lily Ballard
4
Nie ma problemu, Kevin. SO opóźnia od razu wybór odpowiedzi na pytanie, więc musiałem wrócić trochę później. Twoje zdrowie.
Jonathan Sterling
__unsafe_unretained id bself = self;
caleb
4
@JKLaiho: Jasne, też __weakjest w porządku. Jeśli wiesz na pewno, że obiekt nie może znajdować się poza zasięgiem, gdy blok jest wywoływany, __unsafe_unretainedjest to nawet nieco szybsze, ale ogólnie nie ma to znaczenia. Jeśli używasz __weak, upewnij się, że wrzucisz ją do __strongzmiennej lokalnej i przetestuj ją na non-, nilzanim cokolwiek z nią zrobisz.
Lily Ballard
2
@Rpranata: Tak. __blockEfekt uboczny nie zatrzymywania i puszczania był wyłącznie z powodu niemożności prawidłowego uzasadnienia tego. Dzięki ARC kompilator zyskał tę zdolność, więc __blockteraz zachowuje i wydaje. Jeśli chcesz tego uniknąć, musisz użyć polecenia __unsafe_unretained, które instruuje kompilator, aby nie wykonywał żadnych zachowań ani zwolnień wartości w zmiennej.
Lily Ballard
66

Po prostu użyj:

__weak id weakSelf = self;

[someObject someMethodWithBlock:^{
    [weakSelf someOtherMethod];
}];

Więcej informacji: WWDC 2011 - Bloki i Grand Central Dispatch w praktyce .

https://developer.apple.com/videos/wwdc/2011/?id=308

Uwaga: jeśli to nie zadziała, możesz spróbować

__weak typeof(self)weakSelf = self;
3lvis
źródło
2
A czy przypadkiem go znalazłeś :)?
Tieme
2
Możesz obejrzeć film tutaj - developer.apple.com/videos/wwdc/2011/…
nanjunda
Czy jesteś w stanie odwołać się do siebie w „someOtherMethod”? Czy w tym momencie własne ja odniesiemy się do samego siebie, czy też stworzy to również cykl zachowania?
Oren
Cześć @Oren, jeśli spróbujesz odwołać się do siebie w "someOtherMethod", otrzymasz ostrzeżenie Xcode. Moje podejście jest tylko słabym odniesieniem do siebie.
3lvis
1
Otrzymałem ostrzeżenie tylko wtedy, gdy odnosiłem się do siebie bezpośrednio w bloku. Umieszczenie siebie w środku someOtherMethod nie powodowało żadnych ostrzeżeń. Czy to dlatego, że xcode nie jest wystarczająco inteligentny, czy nie jest to problem? Czy odwoływanie się do siebie wewnątrz someOtherMethod odnosiłoby się już do slabegoSelf, skoro tak właśnie wywołujesz metodę?
Oren
22

Może to być oczywiste, ale musisz zrobić brzydki selfalias tylko wtedy, gdy wiesz, że otrzymasz cykl przechowywania. Jeśli blok jest jednorazowy, myślę, że możesz bezpiecznie zignorować zatrzymanie self. Na przykład zły przypadek ma miejsce, gdy masz blok jako interfejs wywołania zwrotnego. Jak tutaj:

typedef void (^BufferCallback)(FullBuffer* buffer);

@interface AudioProcessor : NSObject {…}
@property(copy) BufferCallback bufferHandler;
@end

@implementation AudioProcessor

- (id) init {
    
    [self setBufferCallback:^(FullBuffer* buffer) {
        [self whatever];
    }];
    
}

Tutaj API nie ma większego sensu, ale miałoby sens na przykład podczas komunikacji z superklasą. Zachowujemy obsługę bufora, obsługę bufora zachowuje nas. Porównaj z czymś takim:

typedef void (^Callback)(void);

@interface VideoEncoder : NSObject {…}
- (void) encodeVideoAndCall: (Callback) block;
@end

@interface Foo : NSObject {…}
@property(retain) VideoEncoder *encoder;
@end

@implementation Foo
- (void) somewhere {
    [encoder encodeVideoAndCall:^{
        [self doSomething];
    }];
}

W takich sytuacjach nie wykonuję selfaliasingu. Otrzymujesz cykl zachowania, ale operacja jest krótkotrwała i blok w końcu wyjdzie z pamięci, przerywając cykl. Ale moje doświadczenie z blokami jest bardzo małe i może się zdarzyć, że selfaliasing okaże się najlepszą praktyką na dłuższą metę.

zoul
źródło
6
Słuszna uwaga. To tylko cykl utrzymania, jeśli ja utrzymuje blok przy życiu. W przypadku bloków, które nigdy nie zostaną skopiowane, lub bloków z gwarantowanym ograniczonym czasem trwania (np. Blok ukończenia animacji UIView) nie musisz się tym martwić.
Lily Ballard
6
W zasadzie masz rację. Jednak wykonanie kodu z przykładu spowodowałoby awarię. Właściwości bloku powinny być zawsze deklarowane jako copy, a nie retain. Jeśli są po prostu retain, nie ma gwarancji, że zostaną usunięte ze stosu, co oznacza, że ​​kiedy zaczniesz go wykonywać, już go tam nie będzie. (a kopiowanie i już skopiowany blok jest zoptymalizowany do zachowania)
Dave DeLong,
Ach, pewnie, literówka. Przeszedłem przez tę retainfazę jakiś czas temu i szybko zorientowałem się, o czym mówisz :) Dzięki!
zoul
Jestem prawie pewien, że retainjest to całkowicie ignorowane w przypadku bloków (chyba że zostały już przesunięte ze stosu copy).
Steven Fisher
@Dave DeLong, Nie, to się nie zawiesi, ponieważ @property (retain) jest używane tylko jako odniesienie do obiektu, a nie blok. Nie ma potrzeby używania tutaj kopii ..
DeniziOS
20

Publikowanie kolejnej odpowiedzi, ponieważ to też był problem dla mnie. Początkowo myślałem, że muszę użyć blockSelf wszędzie tam, gdzie wewnątrz bloku było odniesienie do siebie. Tak nie jest, dzieje się tak tylko wtedy, gdy sam obiekt ma w sobie blok. I faktycznie, jeśli użyjesz blockSelf w takich przypadkach, obiekt może zostać zwolniony, zanim otrzymasz wynik z powrotem z bloku, a następnie ulegnie awarii, gdy spróbuje go wywołać, więc wyraźnie chcesz, aby został zachowany do czasu odpowiedzi wraca.

Pierwszy przypadek pokazuje, kiedy wystąpi cykl utrzymania, ponieważ zawiera blok, do którego odwołuje się blok:

#import <Foundation/Foundation.h>

typedef void (^MyBlock)(void);

@interface ContainsBlock : NSObject 

@property (nonatomic, copy) MyBlock block;

- (void)callblock;

@end 

@implementation ContainsBlock
@synthesize block = _block;

- (id)init {
    if ((self = [super init])) {

        //__block ContainsBlock *blockSelf = self; // to fix use this.
        self.block = ^{
                NSLog(@"object is %@", self); // self retain cycle
            };
    }
    return self;
}

- (void)dealloc {
    self.block = nil;
    NSLog (@"ContainsBlock"); // never called.
    [super dealloc];
} 

- (void)callblock {
    self.block();
} 

@end 

 int main() {
    ContainsBlock *leaks = [[ContainsBlock alloc] init];
    [leaks callblock];
    [leaks release];
}

W drugim przypadku nie potrzebujesz blockSelf, ponieważ obiekt wywołujący nie ma w sobie bloku, który spowoduje cykl przechowywania, gdy odwołasz się do siebie:

#import <Foundation/Foundation.h>

typedef void (^MyBlock)(void);

@interface BlockCallingObject : NSObject 
@property (copy, nonatomic) MyBlock block;
@end

@implementation BlockCallingObject 
@synthesize block = _block;

- (void)dealloc {
    self.block = nil;
    NSLog(@"BlockCallingObject dealloc");
    [super dealloc];
} 

- (void)callblock {
    self.block();
} 
@end

@interface ObjectCallingBlockCallingObject : NSObject 
@end

@implementation ObjectCallingBlockCallingObject 

- (void)doneblock {
    NSLog(@"block call complete");
}

- (void)dealloc {
    NSLog(@"ObjectCallingBlockCallingObject dealloc");
    [super dealloc];
} 

- (id)init {
    if ((self = [super init])) {

        BlockCallingObject *myobj = [[BlockCallingObject alloc] init];
        myobj.block = ^() {
            [self doneblock]; // block in different object than this object, no retain cycle
        };
        [myobj callblock];
        [myobj release];
    }
    return self;
}
@end

int main() {

    ObjectCallingBlockCallingObject *myObj = [[ObjectCallingBlockCallingObject alloc] init];
    [myObj release];

    return 0;
} 
possen
źródło
To powszechne nieporozumienie i może być niebezpieczne, ponieważ blokady, które powinny zostać zachowane, selfmogą nie być spowodowane nadmiernym stosowaniem tej poprawki przez ludzi. To dobry przykład unikania cykli przechowywania w kodzie innym niż ARC, dziękujemy za wysłanie.
Carl Veazey,
9

Pamiętaj również, że cykle zachowania mogą wystąpić, jeśli twój blok odnosi się do innego obiektu, który następnie zachowujeself .

Nie jestem pewien, czy Garbage Collection może pomóc w tych cyklach przechowywania. Jeśli obiekt zachowujący blok (który nazwiemy obiektem serwera) przeżyje self(obiekt klienta), odniesienie doself wnętrza bloku nie zostanie uznane za cykliczne, dopóki sam obiekt zatrzymujący nie zostanie zwolniony. Jeśli obiekt serwera znacznie przeżyje swoich klientów, możesz mieć znaczny wyciek pamięci.

Ponieważ nie ma czystych rozwiązań, polecam następujące obejścia. Możesz wybrać jedną lub więcej z nich, aby rozwiązać problem.

  • Używaj bloków tylko do ukończenia , a nie do otwartych wydarzeń. Na przykład użyj bloków dla metod takich jak doSomethingAndWhenDoneExecuteThisBlock:, a nie metod takich jaksetNotificationHandlerBlock: . Bloki użyte do ukończenia mają określony koniec życia i powinny zostać zwolnione przez obiekty serwera po ich ocenie. Zapobiega to zbyt długiemu cyklowi utrzymania, nawet jeśli wystąpi.
  • Zrób ten słaby taniec, który opisałeś.
  • Podaj metodę czyszczenia obiektu przed jego zwolnieniem, co „odłącza” obiekt od obiektów serwera, które mogą zawierać odniesienia do niego; i wywołaj tę metodę przed wywołaniem release na obiekcie. Chociaż ta metoda jest całkowicie w porządku, jeśli twój obiekt ma tylko jednego klienta (lub jest singletonem w pewnym kontekście), ale zepsuje się, jeśli ma wielu klientów. Zasadniczo pokonujesz tutaj mechanizm liczenia retencji; jest to podobne do dzwonienia dealloczamiast release.

Jeśli piszesz obiekt serwera, argumenty blokowe przyjmuj tylko do uzupełnienia. Nie akceptuj argumentów blokowych dla wywołań zwrotnych, takich jak setEventHandlerBlock:. Zamiast tego powróć do klasycznego wzorca delegata: utwórz formalny protokół i rozgłaszaj setEventDelegate:metodę. Nie zatrzymuj pełnomocnika. Jeśli nawet nie chcesz tworzyć formalnego protokołu, zaakceptuj selektor jako wywołanie zwrotne delegata.

I wreszcie, ten wzorzec powinien wywoływać alarmy:

- (void) dealloc {
    [myServerObject releaseCallbackBlocksForObject: self];
    ...
}

Jeśli próbujesz odhaczyć bloki, które mogą odnosić się do selfśrodka dealloc, już masz kłopoty. deallocmoże nigdy nie zostać wywołane z powodu cyklu przechowywania spowodowanego przez odwołania w bloku, co oznacza, że ​​obiekt po prostu wycieknie, dopóki obiekt serwera nie zostanie zwolniony.

Dave R.
źródło
GC pomaga, jeśli używasz __weakodpowiednio.
tc.
Śledzenie czyszczenia pamięci może oczywiście zająć się zachowaniem cykli. Zachowanie cykli jest problemem tylko w środowiskach zliczania referencji
nowość
Wszyscy wiedzą, że odśmiecanie pamięci zostało wycofane w systemie OS X 10.8 na rzecz automatycznego liczenia odwołań (ARC) i ma zostać usunięte w przyszłej wersji systemu OS X ( developer.apple.com/library/mac/#releasenotes / ObjectiveC /… ).
Ricardo Sanchez-Saez
1

__block __unsafe_unretainedmodyfikatory sugerowane w poście Kevina mogą powodować wyjątek złego dostępu w przypadku bloku wykonywanego w innym wątku. Lepiej jest używać tylko modyfikatora __block dla zmiennej temp i po użyciu ustawić ją na zero.

__block SomeType* this = self;
[someObject messageWithBlock:^{
  [this doSomething]; // here would be BAD_ACCESS in case of __unsafe_unretained with
                      //  multithreading and self was already released
  this = nil;
}];
b1gbr0
źródło
Czy naprawdę nie byłoby bezpieczniej użyć po prostu __weak zamiast __block, aby uniknąć potrzeby zerowania zmiennej po jej użyciu? Chodzi mi o to, że to rozwiązanie jest świetne, jeśli chcesz przerwać inne rodzaje cykli, ale z pewnością nie widzę żadnych szczególnych zalet dla zachowania cykli „samodzielnie”.
ale0xB
Nie możesz użyć __weak, jeśli platformą docelową jest iOS 4.x. Czasami też potrzebujesz, aby kod w bloku został wykonany dla prawidłowego obiektu, a nie dla zera.
b1gbr0
1

Możesz użyć biblioteki libextobjc. Jest dość popularny, używany np. W ReactiveCocoa. https://github.com/jspahrsummers/libextobjc

Zapewnia 2 makra @weakify i @strongify, dzięki czemu możesz mieć:

@weakify(self)
[someObject messageWithBlock:^{
   @strongify(self)
   [self doSomething]; 
}];

Zapobiega to bezpośredniemu silnemu odniesieniu, więc nie wchodzimy w cykl zachowania siebie. Zapobiega również zmniejszeniu się wartości self do zera w połowie, ale nadal odpowiednio zmniejsza liczbę zatrzymań. Więcej w tym linku: http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html

Yuri Solodkin
źródło
1
Przed pokazaniem uproszczonego kodu byłoby lepiej wiedzieć, co się za nim kryje, każdy powinien znać dwie rzeczywiste linie kodu.
Alex Cio
0

Co powiesz na to?

- (void) foo {
     __weak __block me = self;

     myBlock = ^ {
        [[me someProp] someMessage];
     }
     ...
 }

Nie dostaję już ostrzeżenia kompilatora.

Καrτhικ
źródło
-1

Blok: wystąpi cykl utrzymania, ponieważ zawiera on blok, do którego istnieje odniesienie w bloku; Jeśli utworzysz kopię bloku i użyjesz zmiennej składowej, self zachowa.

yijiankaka
źródło