Réponse courte
Au lieu d'accéder self
directement, vous devez y accéder indirectement, à partir d'une référence qui ne sera pas conservée. Si vous n'utilisez pas le comptage de référence automatique (ARC) , vous pouvez le faire:
__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
Le __block
mot-clé marque les variables qui peuvent être modifiées à l'intérieur du bloc (nous ne le faisons pas), mais elles ne sont pas non plus automatiquement conservées lorsque le bloc est conservé (sauf si vous utilisez ARC). Si vous faites cela, vous devez être sûr que rien d'autre ne va essayer d'exécuter le bloc après la libération de l'instance MyDataProcessor. (Compte tenu de la structure de votre code, cela ne devrait pas poser de problème.) En savoir plus__block
.
Si vous utilisez ARC , la sémantique des __block
modifications et la référence seront conservées, auquel cas vous devez la déclarer à la __weak
place.
Longue réponse
Disons que vous aviez un code comme celui-ci:
self.progressBlock = ^(CGFloat percentComplete) {
[self.delegate processingWithProgress:percentComplete];
}
Le problème ici est que self conserve une référence au bloc; pendant ce temps, le bloc doit conserver une référence à self afin de récupérer sa propriété de délégué et d'envoyer au délégué une méthode. Si tout le reste de votre application libère sa référence à cet objet, son nombre de retenues ne sera pas nul (car le bloc le pointe) et le bloc ne fait rien de mal (parce que l'objet le pointe) et ainsi la paire d'objets s'infiltrera dans le tas, occupant de la mémoire mais toujours inaccessible sans 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, self retient le bloc, le bloc retient le délégué, et il n'y a pas de cycle (visible d'ici; le délégué peut conserver notre objet mais cela est hors de nos mains en ce moment). Ce code ne risque pas une fuite de la même manière, car la valeur de la propriété déléguée est capturée lors de la création du bloc, au lieu d'être recherchée lors de son exécution. Un effet secondaire est que, si vous modifiez le délégué après la création de ce bloc, le bloc enverra toujours des messages de mise à jour à l'ancien délégué. La probabilité que cela se produise ou non dépend de votre application.
Même si vous étiez 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 passez self
directement au délégué dans l'appel de méthode, vous devez donc 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 le cycle de conservation et appelle toujours le délégué actuel.
Si vous ne pouvez pas modifier le bloc, vous pouvez y faire face . La raison pour laquelle un cycle de rétention est un avertissement, et non une erreur, est qu'il n'épelle pas nécessairement le destin de votre application. Si MyDataProcessor
est capable de libérer les blocs lorsque l'opération est terminée, avant que son parent n'essaye de le libérer, le cycle sera interrompu et tout sera nettoyé correctement. Si vous pouvez en être sûr, la bonne chose à faire serait d'utiliser un #pragma
pour supprimer les avertissements pour ce bloc de code. (Ou utilisez un indicateur de compilation par fichier. Mais ne désactivez pas l'avertissement pour l'ensemble du projet.)
Vous pouvez également chercher à utiliser une astuce similaire ci-dessus, déclarer une référence faible ou non retenue et l'utiliser 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];
}
Les trois éléments ci-dessus vous donneront une référence sans conserver le résultat, bien qu'ils se comportent tous un peu différemment: __weak
tentera de mettre à zéro la référence lorsque l'objet est relâché; __unsafe_unretained
vous laissera un pointeur invalide; __block
ajoutera en fait un autre niveau d'indirection et vous permettra de modifier la valeur de la référence depuis l'intérieur du bloc (non pertinent dans ce cas, car il dp
n'est utilisé nulle part ailleurs).
Le meilleur dépendra du code que vous pouvez modifier et de 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.