J'ai une question sur les références fortes et faibles à l'auto dans les blocs dans iOS. Je sais que la bonne façon de se référer à soi à l'intérieur d'un bloc est de créer une référence faible à l'extérieur du bloc, puis une référence forte à cette référence faible à l'intérieur du bloc, comme ceci:
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^ {
typeof(self) strongSelf = weakSelf;
NSLog(@"%@", strongSelf.someProperty);
});
Cependant, que se passe-t-il si vous avez des blocs imbriqués? Le seul ensemble de références est-il suffisant? Ou avez-vous besoin d'un nouvel ensemble pour chaque bloc? Par exemple, lequel des énoncés suivants est correct?
Cette:
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^ {
typeof(self) strongSelf = weakSelf;
NSLog(@"%@", strongSelf.someProperty);
dispatch_async(dispatch_get_main_queue(), ^ {
strongSelf.view.frame = CGRectZero;
});
});
Ou ca:
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^ {
typeof(self) strongSelf = weakSelf;
NSLog(@"%@", strongSelf.someProperty);
__weak typeof(strongSelf) weakSelf1 = strongSelf;
dispatch_async(dispatch_get_main_queue(), ^ {
typeof(strongSelf) strongSelf1 = weakSelf1;
strongSelf1.view.frame = CGRectZero;
});
});
Toute information ou explication est très appréciée!
Vous n'avez pas besoin de créer deux ensembles de références faibles. Ce que vous voulez éviter avec les blocs, c'est un cycle de rétention, deux objets se maintenant inutilement en vie.
Si j'ai un objet avec cette propriété:
@property (strong) void(^completionBlock)(void);
et j'ai cette méthode:
- (void)doSomething
{
self.completionBlock = ^{
[self cleanUp];
};
[self doLongRunningTask];
}
le bloc sera conservé en vie lorsque je le stockerai dans la propriété completionBlock
. Mais comme il fait référence à self
à l'intérieur du bloc, le bloc gardera self
en vie jusqu'à sa disparition, mais cela ne se produira pas car ils se référencent tous les deux.
Dans cette méthode:
- (void)doSomething
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self cleanUp];
}];
[self doLongRunningTask];
}
vous n'avez pas besoin de faire une référence faible à self
. Le bloc gardera self
vivant, car il fait référence à self
de l'intérieur, mais puisque tout ce que nous faisons, c'est de le transférer à [NSOperationQueue mainQueue]
, self
ne maintient pas le bloc en vie.
J'espère que cela t'aides.
Les deux constructions sont très bien. Cela dépend simplement de votre intention. Que voulez-vous qu'il se passe si l'objet est (a) libéré après le début du bloc externe mais (b) avant le démarrage du bloc interne dans la file d'attente principale? Si vous ne voulez pas qu'il soit conservé dans ce scénario (ce qui pourrait être votre intention, étant donné que vous effectuez cet exercice weakSelf
en premier lieu), utilisez votre dernier exemple, où vous avez le deuxième pointeur faible. Sinon, vous pouvez utiliser votre autre exemple.
Cela dit, quelques observations:
Ce n'est pas une conclusion perdue que vous devez utiliser ce modèle weakSelf
en premier lieu. Certaines personnes pensent à tort qu'elles ont pour utiliser ce modèle weakSelf
pour éviter un cycle de référence fort (aussi appelé cycle de rétention). Mais cet exemple de code ne constitue pas un cycle de référence fort. Il conserve simplement l'objet pendant l'exécution du code distribué, ce qui est une considération très différente.
En fait, parfois vous en avez besoin/voulez. Parfois non. Cela dépend du problème commercial que vous résolvez. Absolument, vous ne voulez souvent pas qu'il garde une référence forte à self
, auquel cas le modèle weakSelf
est parfaitement logique. Mais ce n'est pas toujours le cas.
Mais mon point est que vous ne devriez pas poursuivre ce modèle weakSelf
(au moins dans ce dispatch_async
scénario) pour éviter un cycle de référence fort. Un tel cycle n'existe pas. Lorsque c'est un problème, c'est lorsque vous avez une variable de bloc (par exemple, un bloc completionHandler
). Dans ce cas, le modèle weakSelf
est critique. Mais pas ici.
Mais considérons une seconde ce scénario dans lequel vous ne voulez pas que self
soit conservé. Ensuite, il s'agit de savoir si vous voulez que le code distribué continue du tout en premier lieu. Sinon, vous devriez peut-être utiliser une file d'attente d'opérations avec des opérations annulables au lieu de GCD.
Par exemple, je suis surpris de la fréquence à laquelle les gens agonisent s'ils vont conserver le contrôleur de vue pendant l'exécution d'une demande de réseau en arrière-plan, mais ne vous inquiétez pas s'ils devraient annuler cette demande de réseau en arrière-plan en premier lieu. Souvent, ce dernier est une considération de conception beaucoup plus importante (par exemple, le PDF ou l'image que vous téléchargez prend beaucoup plus de ressources système (mémoire et bande passante réseau) que le contrôleur de vue ne le fera jamais)). .
Mais supposons que (a) vous voulez vraiment que le code distribué continue à s'exécuter, mais (b) vous ne voulez pas conserver self
. (Cela semble être un scénario rare, mais c'est celui que vous avez demandé, alors poursuivons cela.) La dernière question de savoir si vous avez également besoin de votre construction strongSelf
. Dans votre cas, lorsque vous appelez simplement une seule méthode de self
, vous n'avez pas besoin de vous soucier de cette construction strongSelf
. Cela n'est essentiel que si vous allez déférer les ivars ou si vous devez éviter les conditions de course. Mais, dans cet exemple, étant donné qu'un message envoyé à un objet nil
ne fait rien, vous n'avez souvent pas du tout à vous soucier de cette construction strongSelf
.
Ne vous méprenez pas. Il est bon de se déplacer autour du modèle weakSelf
, ainsi que du modèle imbriqué strongSelf
qui l'accompagne parfois. Je suggère simplement qu'il est bon de comprendre quand ces modèles sont vraiment nécessaires. Et je pense que le choix de GCD par rapport à un NSOperation
annulable est souvent une question beaucoup plus critique, mais souvent négligée.
Les blocs sont créés et stockés sur la pile. Ainsi, le bloc sera détruit lorsque la méthode qui a créé le bloc revient.
Si un bloc devient une variable d'instance ARC, copiez le bloc de la pile vers le tas. Vous pouvez explicitement copier un bloc avec le message de copie. Votre bloc est maintenant un bloc basé sur le tas au lieu d'un bloc basé sur la pile. Et vous devez gérer certains problèmes de gestion de la mémoire. Le bloc lui-même conservera une référence forte à tous les objets qu'il référence. Déclarez les pointeurs __weak en dehors du bloc, puis référencez ce pointeur dans le bloc pour éviter les cycles de rétention.