Używanie słabego self w funkcji asynchronicznej wysyłania

Czytałem wiele postów o używaniu __weak self wewnątrz dispatch_async, a teraz jestem trochę zdezorientowany.

Jeśli mam:

self.myQueue = dispatch_queue_create("com.biview.core_data", NULL);

dispatch_async(self.myQueue, ^(void){
    if (!self.var1) {
        self.var1 = ...;
    }
    dispatch_async(dispatch_get_main_queue(), ^(void) {
        if ([self.var2 superview]) {
            [self.var2 removeFromSuperview];
        }

        [self.Label setText:text];
    });
});

Czy muszę używać __weak self. Ponieważ czytałem, że w niektórych przypadkach dispatch_async nie trzeba __weak self.

Zobacz ostatni komentarz tutaj

Author: Community, 2014-02-24

2 answers

Zakładając, self jest wskaźnikiem obiektu do UIViewController.

Rzeczy do rozważenia:

  • A UIViewController jest obiektem "UIKit". Obiekty UIKit nie mogą być wysyłane metodami w wątkach innych niż główne, czyli - metody te muszą być wykonywane tylko w głównym wątku!

  • Blok, który został zapytany w kolejce - czy to był synchronicznie czy asynchronicznie - zostanie ostatecznie wykonany - bez względu na wszystko! No chyba, że program się zakończy zanim to się stanie.

  • Przechwycone retainable silne wskaźniki zostaną zachowane , gdy blok zostanie skopiowany (na przykład, gdy zostanie wysłany asynchronicznie), i ponownie zwolnione , gdy blok zostanie zniszczony (po jego zakończeniu).

  • Przechwycone utrzymywalne słabe wskaźniki nie zostaną zatrzymane i nie zostaną zwolnione.

W Twoim scenariuszu, gdzie uchwycisz siebie w bloku, który jest wysyłane w kolejce głównej , nie musisz się martwić, że złe rzeczy się zdarzają.

Więc dlaczego? A co się właściwie dzieje?

Ponieważ self zostanie przechwycony w bloku, który jest wysyłany asynchronicznie, self zostanie w domyśle zatrzymane, a zwolnione ponownie po zakończeniu bloku.

Oznacza to, że czas życia siebie będzie przedłużony aż do wykończenia bloków. Zauważ, że twój drugi blok jest wysyłany do głównego wątku i masz gwarancję, że self będzie nadal żywy, gdy blok zostanie wykonany.

Powyższa "przedłużona żywotność" może być pożądaną cechą twojego programu.

Jeśli jawnie nie chcesz przedłużać czasu życia obiektu UIViewController, a zamiast tego chcesz, aby blok-kiedy w końcu zostanie wykonany - sprawdź czy ten obiekt {6]} nadal istnieje w wszystko, można użyć _ _ słaby wskaźnik jaźni. Zauważ, że blok zostanie ostatecznie wykonany, bez względu na to, czy UIViewController jest nadal żywy, czy został dealokowany w międzyczasie.

Możesz chcieć, aby blok robił "nic", Jeśli UIViewController został dealokowany przed blok zostanie wykonany:

MyController* __weak weakSelf = self;
dispatch_async(queue, ^{
    MyController* strongSelf = weakSelf;
    if (strongSelf) {
        ...
    }
    else {
       // self has been deallocated in the meantime.
    }
});

Zobacz także: przejście do informacji o wydaniu ARC

Pamiętaj: UIKit obiekty nie powinny być wysyłane metodami w wątkach innych niż główne!

Jeden inny subtelny błąd może wynikać z faktu, że UIKit obiekty powinny wykonywać metody tylko w głównym wątku.

Można to złamać, jeśli blok przechwytuje obiekt UIKit, który jest wysyłany asynchronicznie i jest wykonywany na Nie-głównym wątku. Wtedy może się zdarzyć, że blok zawiera ostatni silne odniesienie do tego UIKit obiektu. Teraz, gdy blok zostanie ostatecznie wykonany, blok zostanie zniszczony, a obiekt UIKit zostanie zwolniony. Ponieważ jest to ostatni silny odniesienie do UIKit obiektu, zostanie on dealokowany. Dzieje się tak jednak w wątku, w którym blok został wykonany - i to nie jest główny wątek! Teraz złe rzeczy mogą (i zwykle będą) się dziać, ponieważ metoda dealloc nadal jest metodą wysyłaną do obiektu UIKit.

Można uniknąć tego błędu, wysyłając blok przechwytujący silny wskaźnik do tego obiektu UIKit i wysyłając do niego metodę atrapy:

UIViewController* strongUIKitPointer = ... 
dispatch_async(non_main_queue, ^{
    ... // do something 
    dispatch(dispatch_get_main_queue(), ^{
        [strongUIKitPointer self];  // note: self is a method, too - doing nothing
    });
});

W Twoim scenariuszu, ostatni strong reference może być tylko w bloku, który wykonuje się w głównym wątku. Więc jesteście bezpieczni od tego subtelnego błędu. ;)

Edit:

W konfiguracji nigdy nie masz cyklu zachowywania. Cykl zachowywania występuje, gdy obiekt A silnie odwołuje się do innego obiektu B, A obiekt B silnie odwołuje się do A. zauważ, że "blok" jest również obiektem dającym się utrzymać.

Wymyślony przykład z cyklicznym odniesieniem:

typedef void(^my_completion_block_t)(NSArray* result);

@interface UsersViewController : UIViewController
@property (nonatomic, copy) my_completion_block_t completion;
@property (nonatomic) NSArray* users;
@end

Tutaj mamy nieruchomość dopełnienie którego typem wartości jest blok. Oznacza to, że otrzymujemy ivar o nazwie _completion, którego typem jest blok.

Klient może ustawić obsługę zakończenia, która powinna być wywołana po zakończeniu określonej operacji. Załóżmy, że operacja pobiera listę użytkowników ze zdalnego serwera. Plan polega na ustawieniu właściwości users Po zakończeniu operacji:

Nieostrożne podejście przypadkowo wprowadziłoby cykliczne odniesienie:

Gdzieś w "UsersViewController.m "

self.completion = ^(NSArray* users){
    self.users = users;
}

[self fetchUsers];  // start asynchronous task

Tutaj, self posiada silne odniesienie do ivar _completion, który jest blokiem. I sam blok przechwytuje self, co powoduje zachowanie self, gdy blok zostanie skopiowany, gdy zostanie wysłany. To klasyczny cykl odniesienia.

Aby uniknąć tego cyklicznego odniesienia, mamy kilka alternatyw:]}
  1. Używając __weak kwalifikowanego wskaźnika self

    UsersViewController* __weak weakSelf = self;
    self.completion = ^(NSArray* users) {
        UsersViewController* strongSelf = weakSelf;
        if (strongSelf) {
            strongSelf.users = users;
        }
        else {
            // the view controller does not exist anymore
        }
    }   
    [usersViewController fetchUsers];
    
  2. Użycie __block kwalifikowanego wskaźnika self i ostatecznie ustawienie go nil w bloku po jego zakończeniu:

    UsersViewController* __block blockSelf = self;
    self.completion = ^(NSArray* users) {
        blockSelf.users = users;
        blockSelf = nil;
    }   
    [usersViewController fetchUsers];
    

Zobacz także: przejście do informacji o wydaniu ARC

 126
Author: CouchDeveloper,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-25 12:32:14

Szybka aktualizacja:

Przykład tego tzw. silnego-słabego tańca w języku swift:

func doSomeThingAsynchronously() {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> () in
        // Do task in default queue
        dispatch_async(dispatch_get_main_queue(), { [weak self] () -> () in
            guard let strongSelf = self else { return }
            // Do task in main queue
            strongSelf.updateView()
        })
    }
}

Popularny projekt open source Alamofire wykorzystuje to podejście.

Przedłużyć żywotność obiektu używając idiomu [weak self] i guard let strongSelf = self else {return}.

Aby uzyskać więcej informacji sprawdź swift-style-guide

Swift 3 update:

func doSomeThingAsynchronously() {
    DispatchQueue.global().async {
        // Do task in default queue
        DispatchQueue.main.async { [weak self] in
            // Do task in main queue
            guard let strongSelf = self else { return }
            strongSelf.updateView()
        }
    }
}
 21
Author: Warif Akhand Rishi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-05 08:44:58