J'ai une application qui fonctionne et je travaille à la convertir en ARC sous Xcode 4.2. L'un des avertissements de pré-vérification implique la capture de self
fortement dans un bloc menant à un cycle de conservation. J'ai créé un exemple de code simple pour illustrer le problème. Je crois comprendre ce que cela signifie, mais je ne suis pas sûr de la "bonne" méthode recommandée pour mettre en œuvre ce type de scénario.
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: qu'est-ce que je fais "mal" et/ou comment cela devrait-il être modifié pour se conformer aux conventions ARC?
Au lieu d'accéder directement à self
, 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 automatique de références (ARC) , vous pouvez procéder comme suit:
__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
Le mot clé __block
marque les variables qui peuvent être modifiées à l'intérieur du bloc (ce n'est pas le cas), mais elles ne sont pas non plus conservées automatiquement lorsque le bloc est conservé (sauf si vous utilisez ARC). Dans ce cas, vous devez vous assurer que rien d'autre ne tentera d'exécuter le bloc après la publication de l'instance MyDataProcessor. (Étant donné la structure de votre code, cela ne devrait pas poser de problème.) En savoir plus sur __block
.
Si vous utilisez ARC , la sémantique de __block
change et la référence est conservée. Dans ce cas, vous devez le déclarer __weak
au lieu.
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 d'extraire sa propriété delegate et envoyer une méthode au délégué. Si tout le reste de votre application libère sa référence à cet objet, son nombre de retenues ne sera pas égal à zéro (car le bloc le pointe) et le bloc ne fait rien de mal (car l'objet pointe vers lui), et ainsi de suite. la paire d'objets s'infiltrera dans le tas, occupant de la mémoire mais inaccessible à tout jamais 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, self retient le bloc, le bloc retient 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 notre portée actuellement). Ce code ne risque pas une fuite de la même manière, car la valeur de la propriété delegate 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 continuera d'envoyer des messages de mise à jour à l'ancien délégué. Que cela soit susceptible de se produire 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 récupérer quelque part. Si vous avez le contrôle sur la définition du type de bloc, le mieux 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 changer le bloc, vous pouvez le gérer . La raison pour laquelle un cycle de conservation est un avertissement et non une erreur est qu’ils n’épellent pas nécessairement Doom pour votre application. Si MyDataProcessor
est capable de libérer les blocs une fois l'opération terminée, le cycle sera interrompu avant que son parent ne tente de le libérer, et tout sera nettoyé correctement. Si vous pouviez 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 compilateur par fichier. Mais ne désactivez pas l'avertissement pour l'ensemble du projet.)
Vous pouvez également utiliser une astuce similaire ci-dessus, en déclarant une référence faible ou non conservée et en l'utilisant 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
essaiera de mettre à zéro la référence lorsque l'objet sera publié; __unsafe_unretained
vous laissera un pointeur invalide; __block
ajoutera un autre niveau d'indirection et vous permettra de modifier la valeur de la référence à l'intérieur du bloc (sans importance dans ce cas, puisque dp
n'est utilisé nulle part ailleurs).
Ce qui est 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.
Il est également possible de supprimer l’avertissement lorsque vous êtes certain que le cycle sera brisé à l’avenir:
#pragma clang diagnostic Push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
self.progressBlock = ^(CGFloat percentComplete) {
[self.delegate processingWithProgress:percentComplete];
}
#pragma clang diagnostic pop
De cette façon, vous n’aurez pas à vous morfondre avec __weak
, self
aliasing et préfixage ivar explicite.
Pour une solution commune, je les ai définis dans l'en-tête de précompilation. Évite la capture et active toujours l'aide du compilateur en évitant d'utiliser id
#define BlockWeakObject(o) __typeof(o) __weak
#define BlockWeakSelf BlockWeakObject(self)
Ensuite, dans le code, vous pouvez faire:
BlockWeakSelf weakSelf = self;
self.dataProcessor.completion = ^{
[weakSelf.delegate myAPIDidFinish:weakSelf];
weakSelf.dataProcessor = nil;
};
Je pense que la solution sans ARC fonctionne également avec ARC, en utilisant le mot clé __block
:
EDIT: Conformément à la passage aux notes de version d'ARC , un objet déclaré avec le stockage __block
est toujours conservé. Utilisez __weak
(préféré) ou __unsafe_unretained
(pour la compatibilité avec les versions antérieures).
// 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];
En combinant quelques autres réponses, voici ce que j'utilise maintenant pour un moi faible typé à utiliser dans les blocs:
__typeof(self) __weak welf = self;
J'ai défini cela comme un extrait de code XCode avec un préfixe de complétion "welf" dans les méthodes/fonctions, qui apparaît après avoir tapé uniquement "nous".
warning => "se capturer à l'intérieur du bloc est susceptible de conduire à un cycle de rétention"
lorsque vous vous référez à vous-même ou à sa propriété à l'intérieur d'un bloc qui est fortement retenu par vous-même, il est indiqué plus haut.
donc pour l'éviter, nous devons en faire une semaine de réf
__weak typeof(self) weakSelf = self;
donc au lieu d'utiliser
blockname=^{
self.PROPERTY =something;
}
nous devrions utiliser
blockname=^{
weakSelf.PROPERTY =something;
}
remarque: le cycle de conservation se produit généralement lorsque deux objets se référant l'un à l'autre et dont le nombre de références est égal à 1 et dont la méthode delloc n'est jamais appelée.
La nouvelle façon de faire est d'utiliser @weakify et @strongify marco
@weakify(self);
[self methodThatTakesABlock:^ {
@strongify(self);
[self doSomething];
}];