silne uchwycenie siebie w tym bloku prawdopodobnie doprowadzi do cyklu podtrzymania

207

Jak mogę uniknąć tego ostrzeżenia w xcode. Oto fragment kodu:

[player(AVPlayer object) addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
queue:nil usingBlock:^(CMTime time) {
    current+=1;

    if(current==60)
    {
        min+=(current/60);
        current = 0;
    }

    [timerDisp(UILabel) setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];///warning occurs in this line
}];
użytkownik1845209
źródło
Czy timerDispnieruchomość jest w klasie?
Tim
Tak, @property (nieatomowy, silny) UILabel * timerDisp;
user1845209
2
Co to jest: player(AVPlayer object)i timerDisp(UILabel)?
Carl Veazey
Odtwarzacz AVPlayer *; UILabel * timerDisp;
user1845209
5
Prawdziwe pytanie brzmi: jak wyciszyć to ostrzeżenie bez niepotrzebnego słabego odniesienia na sobie, gdy wiadomo, że odwołanie cykliczne zostanie zerwane (np. Czy zawsze usuwa się odniesienie po zakończeniu żądania sieci).
Glenn Maynard

Odpowiedzi:

514

Przechwytywanie selftutaj nadchodzi z twoim niejawnym dostępem do właściwości self.timerDisp- nie możesz odwoływać się do selfwłaściwości selfz bloku, który zostanie mocno zatrzymany self.

Możesz obejść ten problem, tworząc słabe odniesienie selfprzed dostępem timerDispdo bloku:

__weak typeof(self) weakSelf = self;
[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
                                     queue:nil
                                usingBlock:^(CMTime time) {
                                                current+=1;

                                                if(current==60)
                                                {
                                                    min+=(current/60);
                                                    current = 0;
                                                }

                                                 [weakSelf.timerDisp setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];
                                            }];
Tim
źródło
13
Spróbuj użyć __unsafe_unretainedzamiast tego.
Tim
63
Zdecydowany. użyj tego zamiast tego: __unsafe_unretained typeof (self) poorSelf = self; dzięki za pomoc @Tim
user1845209 28.01.2013
1
Dobra odpowiedź, ale mam mały problem z tym, że mówisz: „nie możesz odnosić się do siebie lub właściwości na siebie z bloku, który będzie silnie zachowany przez siebie”. To nie jest do końca prawda. Zobacz moją odpowiedź poniżej. Lepiej powiedzieć: „Państwo musi zachować wielką ostrożność, jeśli odnoszą się do siebie ...”
Chris Suter
8
Nie widzę cyklu przechowywania w kodzie OP. Blok nie jest silnie zachowany przez self, jest utrzymywany przez główną kolejkę wysyłkową. Czy się mylę?
erikprice
3
@erikprice: nie pomylisz się. Zinterpretowałem to pytanie przede wszystkim o błędzie, jaki przedstawia Xcode („Jak mogę uniknąć tego ostrzeżenia w xcode”), a nie o faktycznej obecności cyklu przechowywania. Masz rację mówiąc, że żaden cykl przechowywania nie jest widoczny tylko z dostarczonego fragmentu OP.
Tim
52
__weak MyClass *self_ = self; // that's enough
self.loadingDidFinishHandler = ^(NSArray *receivedItems, NSError *error){
    if (!error) {
       [self_ showAlertWithError:error];
    } else {
       self_.items = [NSArray arrayWithArray:receivedItems];
       [self_.tableView reloadData];
    }
};

I jedną bardzo ważną rzecz do zapamiętania: nie używaj zmiennych instancji bezpośrednio w bloku, użyj jej jako właściwości słabego obiektu, przykład:

self.loadingDidFinishHandler = ^(NSArray *receivedItems, NSError *error){
        if (!error) {
           [self_ showAlertWithError:error];
        } else {
           self_.items = [NSArray arrayWithArray:receivedItems];
           [_tableView reloadData]; // BAD! IT ALSO WILL BRING YOU TO RETAIN LOOP
        }
 };

i nie zapomnij zrobić:

- (void)dealloc {
    self.loadingCompletionHandler = NULL;
}

inny problem może pojawić się, jeśli przekażesz słabą kopię nie zatrzymanego przez nikogo obiektu:

MyViewController *vcToGo = [[MyViewCOntroller alloc] init];
__weak MyViewController *vcToGo_ = vcToGo;
self.loadingCompletion = ^{
    [vcToGo_ doSomePrecessing];
};

jeśli vcToGozostanie zwolniony, a następnie ten blok zostanie uruchomiony, wierzę, że nastąpi awaria z nierozpoznanym selektorem do kosza, który vcToGo_teraz zawiera zmienną. Spróbuj to kontrolować.

iiFreeman
źródło
3
To byłaby silniejsza odpowiedź, jeśli również to wyjaśnisz.
Eric J.
43

Lepsza wersja

__strong typeof(self) strongSelf = weakSelf;

Utwórz silne odniesienie do tej słabej wersji jako pierwszej linii w swoim bloku. Jeśli self nadal istnieje, gdy blok zaczyna się wykonywać i nie spadł do zera, linia ta zapewnia, że ​​będzie się utrzymywał przez cały czas wykonywania bloku.

Cała sprawa wyglądałaby tak:

// Establish the weak self reference
__weak typeof(self) weakSelf = self;

[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
                                 queue:nil
                            usingBlock:^(CMTime time) {

    // Establish the strong self reference
    __strong typeof(self) strongSelf = weakSelf;

    if (strongSelf) {
        [strongSelf.timerDisp setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];
    } else {
        // self doesn't exist
    }
}];

Przeczytałem ten artykuł wiele razy. To jest doskonały artykuł Erici Sadun na temat unikania problemów podczas korzystania z bloków i NSNotificationCenter


Szybka aktualizacja:

Na przykład w trybie szybkim prostą metodą z blokiem sukcesu byłoby:

func doSomeThingWithSuccessBlock(success: () -> ()) {
    success()
}

Kiedy wywołujemy tę metodę i trzeba jej użyć selfw bloku sukcesu. Będziemy używać funkcji [weak self]i guard let.

    doSomeThingWithSuccessBlock { [weak self] () -> () in
        guard let strongSelf = self else { return }
        strongSelf.gridCollectionView.reloadData()
    }

Ten tak zwany silny i słaby taniec wykorzystywany jest w popularnym projekcie open source Alamofire.

Aby uzyskać więcej informacji, zapoznaj się z przewodnikiem po szybkim stylu

Warif Akhand Rishi
źródło
Co jeśli zrobiłeś typeof(self) strongSelf = self;poza blokiem (zamiast __weak), to w bloku powiedział strongSelf = nil;po użyciu? Nie widzę, jak twój przykład zapewnia, że ​​słabySelf nie jest zerowy do czasu wykonania bloku.
Matt
Aby uniknąć możliwych cykli zachowania, ustalamy słabe odniesienie do siebie poza każdym blokiem, który używa self w swoim kodzie. W swój sposób musisz upewnić się, że blok jest wykonywany. Kolejny blok kodu ur jest teraz odpowiedzialny za zwolnienie ur zachowanej wcześniej pamięci.
Warif Akhand Rishi
@Matt celem tego przykładu nie jest zachowanie słabegoSelf. Celem jest, jeśli słabySelf nie ma wartości zero, wykonaj silne odniesienie wewnątrz bloku. Więc kiedy blok zacznie wykonywać się sam, ja nie staje się zerowy wewnątrz bloku.
Warif Akhand Rishi
15

W innej odpowiedzi Tim powiedział:

nie możesz odnosić się do siebie lub właściwości na siebie z bloku, który będzie silnie zachowany przez siebie.

To nie do końca prawda. Możesz to robić, dopóki w pewnym momencie przerwiesz cykl. Załóżmy na przykład, że masz timer, który strzela, który ma blok, który zachowuje siebie, a także masz silne odniesienie do timera w sobie. Jest to całkowicie w porządku, jeśli zawsze wiesz, że w pewnym momencie zniszczysz stoper i przerwiesz cykl.

W moim przypadku miałem teraz ostrzeżenie dla kodu, który:

[x setY:^{ [x doSomething]; }];

Teraz zdaję sobie sprawę, że clang wygeneruje to ostrzeżenie tylko wtedy, gdy wykryje, że metoda zaczyna się od „set” (i jeszcze jednego specjalnego przypadku, o którym nie wspomnę tutaj). Dla mnie wiem, że nie ma niebezpieczeństwa wystąpienia pętli zatrzymania, więc zmieniłem nazwę metody na „useY:” Oczywiście, może to nie być odpowiednie we wszystkich przypadkach i zwykle będziesz chciał użyć słabego odniesienia, ale Pomyślałem, że warto zwrócić uwagę na moje rozwiązanie na wypadek, gdyby pomogło innym.

Chris Suter
źródło
4

Wiele razy tak naprawdę nie jest to cykl przechowywania .

Jeśli wiesz, że tak nie jest, nie musisz wprowadzać na świat bezowocnych słabych Samotnych.

Apple nawet wymusza na nas te ostrzeżenia za pomocą interfejsu API UIPageViewController, który obejmuje ich ustawioną metodę (która wyzwala te ostrzeżenia - jak wspomniano w innym miejscu - myśląc, że ustawiasz wartość dla ivar, który jest blokiem) i blok obsługi zakończenia (w którym bez wątpienia będziesz odnosić się do siebie).

Oto kilka dyrektyw kompilatora, aby usunąć ostrzeżenie z tego jednego wiersza kodu:

#pragma GCC diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
    [self.pageViewController setViewControllers:@[newViewController] direction:navigationDirection animated:YES completion:^(BOOL finished) {
        // this warning is caused because "setViewControllers" starts with "set…", it's not a problem
        [self doTheThingsIGottaDo:finished touchThePuppetHead:YES];
    }];
#pragma GCC diagnostic pop
bshirley
źródło
1

Dodanie dwóch centów za poprawę precyzji i stylu. W większości przypadków użyjesz tylko jednego lub kilku członków selfw tym bloku, najprawdopodobniej tylko w celu aktualizacji suwaka. Casting selfto przesada. Zamiast tego lepiej jest być jawnym i rzucać tylko te obiekty, których naprawdę potrzebujesz w bloku. Na przykład, jeśli jest to przykład UISlider*, powiedzmy, _timeSliderwykonaj następujące czynności przed deklaracją bloku:

UISlider* __weak slider = _timeSlider;

Następnie wystarczy użyć sliderwewnątrz bloku. Technicznie jest to bardziej precyzyjne, ponieważ zawęża potencjalny cykl zatrzymania tylko do potrzebnego obiektu, a nie do wszystkich obiektów w środku self.

Pełny przykład:

UISlider* __weak slider = _timeSlider;
[_embeddedPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 1)
     queue:nil
     usingBlock:^(CMTime time){
        slider.value = time.value/time.timescale;
     }
];

Dodatkowo, najprawdopodobniej obiekt rzucany na słaby wskaźnik jest już wewnątrz słabym wskaźnikiem, selfa także minimalizuje lub całkowicie eliminuje prawdopodobieństwo cyklu zatrzymania. W powyższym przykładzie _timeSliderwłaściwość jest przechowywana jako słaby odnośnik, np .:

@property (nonatomic, weak) IBOutlet UISlider* timeSlider;

Pod względem stylu kodowania, podobnie jak w C i C ++, deklaracje zmiennych lepiej czytać od prawej do lewej. Deklarowanie SomeType* __weak variablew tej kolejności brzmi bardziej naturalnie od prawej do lewej, jak: variable is a weak pointer to SomeType.

Luis Artola
źródło
1

Niedawno natrafiłem na to ostrzeżenie i chciałem je trochę lepiej zrozumieć. Po kilku próbach i błędach odkryłem, że pochodzi ona od tego, że metoda zaczyna się od „dodaj” lub „zapisz”. Cel C traktuje nazwy metod zaczynające się od „nowy”, „przydziel” itp. Jako zwracający zachowany obiekt, ale nie wspomina (że mogę znaleźć) niczego o „dodawaniu” lub „zapisywaniu”. Jeśli jednak użyję nazwy metody w ten sposób:

[self addItemWithCompletionBlock:^(NSError *error) {
            [self done]; }];

Zobaczę ostrzeżenie na linii [self done]. Nie spowoduje to jednak:

[self itemWithCompletionBlock:^(NSError *error) {
    [self done]; }];

Pójdę naprzód i użyję metody „__weak __typeof (self) poorSelf = self”, aby odwołać się do mojego obiektu, ale tak naprawdę nie lubię tego robić, ponieważ to dezorientuje przyszłego mnie i / lub innego programistę. Oczywiście nie mogłem również użyć „dodaj” (lub „zapisz”), ale to gorzej, ponieważ odbiera to znaczenie tej metody.

Ray M.
źródło