222 votes

Comment éviter auto capture dans des blocs lors de l’implémentation d’une API ?

Je dispose d'une application et je suis en train de travailler sur la conversion à l'ARC dans Xcode 4.2. L'un des pré-vérifier les avertissements consiste à capturer self fortement dans un bloc conduisant à un cycle de conserver. J'ai fait un simple exemple de code pour illustrer le problème. Je crois que je comprends ce que cela signifie, mais je ne suis pas sûr de le "corriger" ou de la manière recommandée pour mettre en œuvre ce type de scénario.

  • l'auto est une instance de la classe MyAPI
  • le code ci-dessous est simplifié pour ne montrer que les interactions avec les objets et les blocs utiles à ma question
  • supposons que MyAPI obtient des données à partir d'une source distante et MyDataProcessor travaille sur les données et produit une sortie
  • le processeur est configuré avec des blocs de communiquer les progrès et l'état

exemple de code:

// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

self.dataProcessor.progress = ^(CGFloat percentComplete) {
    [self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};

self.dataProcessor.completion = ^{
    [self.delegate myAPIDidFinish:self];
    self.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];

Question: que suis-je en train de faire "mal" et/ou comment devrait-il être modifié pour se conformer à l'ARC conventions?

509voto

benzado Points 36367

Réponse courte

Au lieu d'accéder self directement, vous devriez accéder indirectement, à partir d'une référence qui ne sera pas conservée. Si vous n'êtes pas en utilisant Automatique de Comptage de Référence (ARC), vous pouvez faire ceci:

__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
    [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}

L' __block mot-clé marques de variables qui peuvent être modifiées à l'intérieur du bloc (nous ne faisons pas que), mais aussi qu'ils ne sont pas retenus lorsque le bloc est conservé (sauf si vous utilisez un ARC). Si vous faites cela, vous devez être sûr que rien d'autre ne se passe à essayer d'exécuter le bloc après le MyDataProcessor instance est libéré. (Compte tenu de la structure de votre code, cela ne devrait pas être un problème). Lire plus à propos de __block.

Si vous utilisez un ARC, la sémantique de l' __block changements et la référence sera conservé, dans ce cas, vous devez le déclarer __weak à la place.

Réponse longue

Disons que vous avez eu un code comme ceci:

self.progressBlock = ^(CGFloat percentComplete) {
    [self.delegate processingWithProgress:percentComplete];
}

Le problème ici est que l'auto est maintenant une référence pour le bloc; pendant ce temps, le bloc doit conserver une référence à l'auto pour aller chercher son délégué propriété et envoyer le délégué d'une méthode. Si tout le reste dans votre application libère sa référence à cet objet, sa conserver le comte de ne pas être égale à zéro (parce que le bloc est le montrant du doigt) et le bloc n'est pas de faire quelque chose de mal (parce que l'objet est le montrant du doigt) et la paire d'objets de fuite dans le tas, d'occupation de la mémoire, mais à jamais inaccessible sans un débogueur. Tragique, vraiment.

Ce cas pourrait être facilement résolu en faisant ceci à la place:

id progressDelegate = self.delegate;
self.progressBlock = ^(CGFloat percentComplete) {
    [progressDelegate processingWithProgress:percentComplete];
}

Dans ce code, l'auto est en conservant le bloc, le bloc est de conserver le délégué, et il n'y a pas de cycles (visible à partir d'ici; le délégué peut conserver notre objet, mais c'est hors de nos mains en ce moment). Ce code ne sera pas le risque d'une fuite de la même manière, parce que la valeur du délégué de la propriété est capturée lorsque le bloc est créé, au lieu de regardé quand il s'exécute. Un effet secondaire est que, si vous modifiez le délégué après ce bloc est créé, le bloc va encore envoyer des messages de mise à jour de l'ancien délégué. Si cela est susceptible de se produire ou non dépend de votre application.

Même si vous avez été cool avec ce comportement, vous ne pouvez toujours pas utiliser cette astuce dans votre cas:

self.dataProcessor.progress = ^(CGFloat percentComplete) {
    [self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};

Ici vous êtes de passage self directement au délégué à l'appel de la méthode, de sorte que vous devez le faire, quelque part. Si vous avez le contrôle sur la définition du type de bloc, la meilleure chose serait de passer le délégué dans le bloc en tant que paramètre:

self.dataProcessor.progress = ^(MyDataProcessor *dp, CGFloat percentComplete) {
    [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
};

Cette solution évite les conserver cycle et appelle toujours l'actuel délégué.

Si vous ne pouvez pas changer le bloc, vous pouvez traiter avec elle. La raison pour conserver un cycle est un avertissement, pas une erreur, c'est qu'ils n'ont pas nécessairement catastrophique pour votre application. Si MyDataProcessor a la possibilité de libérer les blocs lorsque l'opération est terminée, avant que sa mère serait tenter de le libérer, le cycle sera brisé et tout sera nettoyé correctement. Si vous pouviez être sûr de cela, alors que la bonne chose à faire serait d'utiliser un #pragma de supprimer les mises en garde pour ce bloc de code. (Ou utiliser un fichier par fichier compilateur drapeau. Mais ne pas désactiver l'avertissement pour l'ensemble du projet.)

Vous pouvez aussi rechercher dans l'aide d'une astuce similaire ci-dessus, la déclaration d'une référence de faiblesse ou de consignes et de l'aide que dans le bloc. Par exemple:

__weak MyDataProcessor *dp = self; // OK for iOS 5 only
__unsafe_unretained MyDataProcessor *dp = self; // OK for iOS 4.x and up
__block MyDataProcessor *dp = self; // OK if you aren't using ARC
self.progressBlock = ^(CGFloat percentComplete) {
    [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}

Tous les trois ci-dessus vous donnera une référence sans conserver le résultat, mais tous, ils se comportent un peu différemment: __weak va essayer de zéro de référence lorsque l'objet est libéré, __unsafe_unretained vous laisse avec un pointeur non valide; __block sera effectivement ajouter un autre niveau d'indirection et vous permettent de modifier la valeur de référence à partir de l'intérieur du bloc (pas pertinent dans ce cas, depuis dp n'est pas utilisé n'importe où ailleurs).

Ce qui est le mieux dépendra de ce code, vous êtes en mesure de changer et ce que vous ne pouvez pas. Mais j'espère que cela vous a donné quelques idées sur la façon de procéder.

25voto

zoul Points 51637

Il y a aussi la possibilité de supprimer le message d’avertissement vous êtes positif que le cycle obtiendrez cassé à l’avenir :

De cette façon, vous n’avez pas à singe avec , aliasing et préfixant ivar explicite.

14voto

dmpontifex Points 346

Pour une solution commune, j’ai ces définir dans l’en-tête precompile. Évite les capture et permet encore de compilateur aide en évitant d’utiliser``

Puis, dans le code vous pouvez faire :

11voto

Tony Points 2331

Je crois que la solution sans ARC travaille également à l'ARC, à l'aide de l' __block mot-clé:

EDIT: Par la Transition à l'ARC Notes de Version, un objet déclaré avec __block stockage est toujours conservé. Utiliser __weak (de préférence) ou __unsafe_unretained (pour la compatibilité ascendante).

// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

// Use this inside blocks
__block id myself = self;

self.dataProcessor.progress = ^(CGFloat percentComplete) {
    [myself.delegate myAPI:myself isProcessingWithProgress:percentComplete];
};

self.dataProcessor.completion = ^{
    [myself.delegate myAPIDidFinish:myself];
    myself.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];

11voto

La combinaison de quelques autres réponses, c’est ce que j’utilise maintenant pour un faible typé auto à utiliser dans des blocs :

J’ai mis qu’en tant qu' Extrait de Code de XCode avec le préfixe achèvement « welf » dans les fonctions/méthodes, qui frappe après avoir tapé « nous seuls ».

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X