web-dev-qa-db-fra.com

Utilisation de self faible dans la fonction dispatch_async

J'ai lu beaucoup d'articles sur l'utilisation de __weak self à l'intérieur dispatch_async, et maintenant je suis un peu confus.

si j'ai :

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];
    });
});

dois-je utiliser __weak self. Parce que je lis que dans certains cas dispatch_async pas besoin d'un __weak self.

Voir le dernier commentaire ici

50
Marko Zadravec

En supposant que self est un pointeur d'objet vers un UIViewController.

Choses à considérer:

  • Un UIViewController est un objet "UIKit". Les objets UIKit ne doivent pas recevoir de méthodes sur les threads non principaux, c'est-à-dire que ces méthodes doivent s'exécuter uniquement sur le thread principal!

  • Un bloc qui a été mis en file d'attente dans une file d'attente - que ce soit de manière synchrone ou asynchrone - sera finalement exécuté - quoi qu'il arrive! Eh bien, à moins que le programme ne se termine avant que cela ne se produise.

  • Les pointeurs forts captables capturés seront conservés lorsque le bloc sera copié ( par exemple, lorsqu'il est distribué de manière asynchrone), et à nouveau libéré lorsque le bloc sera détruit (une fois terminé).

  • Les pointeurs conservables capturés faibles ne seront PAS conservés et ne seront pas libérés.

Dans votre scénario, où vous capturez vous-même dans le bloc qui est distribué dans la file d'attente principale , vous n'avez pas à vous soucier que de mauvaises choses se produisent.

Alors pourquoi? Et que se passe-t-il réellement?

Puisque self sera capturé dans le bloc qui est distribué asynchrone , self sera implicitement conservé , et relâché une fois le bloc terminé.

Cela signifie que la durée de vie de soi sera prolongée jusqu'à après la fin du bloc. Notez que votre deuxième bloc est distribué sur le thread principal , et c'est garanti que self est toujours vivant lorsque ce bloc est exécuté.

Cette "durée de vie prolongée" ci-dessus, pourrait être une caractéristique souhaitée de votre programme.

Si vous explicitement ne voulez pas prolonger la durée de vie de l'objet UIViewController, et voulez plutôt le bloc - quand il a finalement exécute - vérifie si cet objet UIViewController existe toujours, vous pouvez utiliser un pointeur __weak de soi. Notez que le bloc est finalement exécuté, que le UIViewController soit toujours vivant ou ait été désalloué entre-temps.

Vous voudrez peut-être que le bloc ne fasse "rien" si le UIViewController a été désalloué avant le bloc sera exécuté:

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

Voir aussi: Transitioning to ARC Release Notes

N'oubliez pas: UIKit les objets ne doivent pas recevoir de méthodes sur les threads non principaux!

Une autre erreur subtile peut se produire du fait que les objets UIKit doivent exécuter des méthodes uniquement sur le thread principal.

Cela peut être violé si un bloc capture un objet UIKit qui est distribué de manière asynchrone et s'exécute sur un thread non principal . Il peut alors arriver que le bloc contienne la dernière référence forte à cet objet UIKit. Maintenant, lorsque le bloc sera finalement exécuté, le bloc sera détruit et l'objet UIKit sera libéré. Comme il s'agit de la dernière référence forte à l'objet UIKit, elle sera désallouée. Cependant, cela se produit sur le thread où le bloc a été exécuté - et ce n'est pas le thread principal! Maintenant, de mauvaises choses peuvent (et se produiront généralement), car la méthode dealloc est toujours une méthode envoyée à un objet UIKit.

Vous pouvez éviter cette erreur en distribuant un bloc capturant un pointeur fort vers cet objet UIKit et en lui envoyant une méthode fictive:

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

Dans votre scénario cependant, la dernière référence forte pourrait être uniquement dans le bloc qui s'exécute sur le thread principal. Vous êtes donc à l'abri de cette subtile erreur. ;)

Modifier:

Dans votre configuration, vous n'avez jamais de cycle de conservation. Un cycle de rétention se produit si un objet pouvant être retenu A fait fortement référence à un autre objet pouvant être retenu B, et que l'objet B fait fortement référence à A. Notez qu'un "Bloc" est également un objet pouvant être retenu.

Un exemple artificiel avec une référence cyclique:

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

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

Ici, nous avons une propriété de complétion dont le type de valeur est un bloc. Autrement dit, nous obtenons un ivar avec le nom _completion dont le type est un bloc.

Un client peut définir un gestionnaire d'achèvement qui doit être appelé lorsqu'une certaine opération est terminée. Supposons que l'opération récupère une liste d'utilisateurs à partir d'un serveur distant. Le plan consiste à définir la propriété utilisateurs une fois l'opération terminée:

L'approche imprudente introduirait accidentellement une référence cyclique:

Quelque part dans "UsersViewController.m"

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

[self fetchUsers];  // start asynchronous task

Ici, self contient une forte référence à l'ivar _completion, qui est un bloc. Et le bloc lui-même capture self , ce qui provoque le maintien self lorsque le le bloc est copié lors de son envoi. Il s'agit d'un cycle de référence classique.

Afin d'éviter cette référence cyclique, nous avons quelques alternatives:

  1. Utilisant un __weak pointeur qualifié de 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. Utilisant un __block pointeur qualifié de self et éventuellement le définissant nil dans le bloc à la fin:

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

Voir aussi: Transitioning to ARC Release Notes

132
CouchDeveloper

Mise à jour rapide:

Un exemple de cette soi-disant danse forte-faible dans Swift:

Swift 4.2:

func doSomeThingAsynchronously() {
    DispatchQueue.global().async {
        // Do task in default queue
        DispatchQueue.main.async { [weak self] in
            // Do task in main queue
            guard let self = self else { return }
            self.updateView()
        }
    }
}

Swift 3 & 4:

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()
        }
    }
}

Swift 2:

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()
        })
    }
}

Le projet open source populaire Alamofire utilise cette approche.

Prolongez la durée de vie des objets en utilisant [self faible] et guard let strongSelf = self else {return} idiome.

Pour plus d'informations, consultez Swift-style-guide

27