Retenir le cycle sur `self` avec des blocs

J’ai bien peur que cette question soit assez simple, mais je pense que cela concerne beaucoup de programmeurs Objective-C qui entrent dans les blocs.

Ce que j’ai entendu, c’est que puisque les blocs capturent les variables locales référencées en tant que copies const , l’utilisation de self dans un bloc peut entraîner un cycle de conservation, si ce bloc est copié. Donc, nous sums supposés utiliser __block pour forcer le bloc à traiter directement avec self au lieu de le copier.

 __block typeof(self) bself = self; [someObject messageWithBlock:^{ [bself doSomething]; }]; 

au lieu de juste

 [someObject messageWithBlock:^{ [self doSomething]; }]; 

Ce que j’aimerais savoir, c’est ce qui suit: si c’est vrai, y a-t-il un moyen d’éviter la laideur (en dehors de l’utilisation du GC)?

    Ssortingctement parlant, le fait que ce soit une copie const n’a rien à voir avec ce problème. Les blocs conservent toutes les valeurs obj-c capturées lors de leur création. Il se trouve que la solution de contournement pour le problème de const-copy est identique à la solution de contournement pour le problème de conservation; à savoir, en utilisant la classe de stockage __block pour la variable.

    En tout cas, pour répondre à votre question, il n’y a pas de réelle alternative ici. Si vous concevez votre propre API basée sur des blocs, et que cela soit logique, vous pouvez faire en sorte que le bloc reçoive la valeur de self en argument. Malheureusement, cela n’a pas de sens pour la plupart des API.

    Veuillez noter que le référencement d’un ivar a exactement le même problème. Si vous avez besoin de référencer un ivar dans votre bloc, utilisez plutôt une propriété ou utilisez-la bself->ivar .


    Addendum: lors de la compilation sous ARC, __block ne rompt plus les cycles de rétention. Si vous comstackz pour ARC, vous devez utiliser __weak ou __unsafe_unretained place.

    Utilisez simplement:

     __weak id weakSelf = self; [someObject someMethodWithBlock:^{ [weakSelf someOtherMethod]; }]; 

    Pour plus d’informations: WWDC 2011 – Blocs et Grand Central Dispatch in Practice .

    https://developer.apple.com/videos/wwdc/2011/?id=308

    Remarque: si cela ne fonctionne pas, vous pouvez essayer

     __weak typeof(self)weakSelf = self; 

    Cela peut être évident, mais vous n’avez qu’à faire le self alias lorsque vous savez que vous obtiendrez un cycle de rétention. Si le bloc est juste un coup, alors je pense que vous pouvez ignorer en toute sécurité le maintien sur self . Le mauvais exemple est lorsque vous avez le bloc comme interface de rappel, par exemple. Comme ici:

     typedef void (^BufferCallback)(FullBuffer* buffer); @interface AudioProcessor : NSObject {…} @property(copy) BufferCallback bufferHandler; @end @implementation AudioProcessor - (id) init { … [self setBufferCallback:^(FullBuffer* buffer) { [self whatever]; }]; … } 

    Ici, l’API n’a pas beaucoup de sens, mais cela aurait du sens lors de la communication avec une super-classe, par exemple. Nous conservons le gestionnaire de tampons, le gestionnaire de tampons nous conserve. Comparez avec quelque chose comme ceci:

     typedef void (^Callback)(void); @interface VideoEncoder : NSObject {…} - (void) encodeVideoAndCall: (Callback) block; @end @interface Foo : NSObject {…} @property(retain) VideoEncoder *encoder; @end @implementation Foo - (void) somewhere { [encoder encodeVideoAndCall:^{ [self doSomething]; }]; } 

    Dans ces situations, je ne fais pas le pseudonyme. Vous obtenez un cycle de conservation, mais l’opération est de courte durée et le bloc finira par perdre sa mémoire, interrompant le cycle. Mais mon expérience avec les blocs est très petite et il se peut que l’ self aliasing soit considéré comme une meilleure pratique à long terme.

    Poster une autre réponse parce que c’était un problème pour moi aussi. Au départ, je pensais que je devais utiliser blockSelf partout où il y avait une référence à l’intérieur d’un bloc. Ce n’est pas le cas, ce n’est que lorsque l’object lui-même contient un bloc. Et en fait, si vous utilisez blockSelf dans ces cas-là, l’object peut être désalloué avant que vous n’obteniez le résultat du bloc, puis il se bloquera lorsqu’il essaiera de l’appeler. revient.

    Le premier cas montre qu’un cycle de conservation se produira car il contient un bloc référencé dans le bloc:

     #import  typedef void (^MyBlock)(void); @interface ContainsBlock : NSObject @property (nonatomic, copy) MyBlock block; - (void)callblock; @end @implementation ContainsBlock @synthesize block = _block; - (id)init { if ((self = [super init])) { //__block ContainsBlock *blockSelf = self; // to fix use this. self.block = ^{ NSLog(@"object is %@", self); // self retain cycle }; } return self; } - (void)dealloc { self.block = nil; NSLog (@"ContainsBlock"); // never called. [super dealloc]; } - (void)callblock { self.block(); } @end int main() { ContainsBlock *leaks = [[ContainsBlock alloc] init]; [leaks callblock]; [leaks release]; } 

    Vous n’avez pas besoin de blockSelf dans le second cas car l’object appelant ne contient pas de bloc qui provoquera un cycle de conservation lorsque vous faites référence à vous-même:

     #import  typedef void (^MyBlock)(void); @interface BlockCallingObject : NSObject @property (copy, nonatomic) MyBlock block; @end @implementation BlockCallingObject @synthesize block = _block; - (void)dealloc { self.block = nil; NSLog(@"BlockCallingObject dealloc"); [super dealloc]; } - (void)callblock { self.block(); } @end @interface ObjectCallingBlockCallingObject : NSObject @end @implementation ObjectCallingBlockCallingObject - (void)doneblock { NSLog(@"block call complete"); } - (void)dealloc { NSLog(@"ObjectCallingBlockCallingObject dealloc"); [super dealloc]; } - (id)init { if ((self = [super init])) { BlockCallingObject *myobj = [[BlockCallingObject alloc] init]; myobj.block = ^() { [self doneblock]; // block in different object than this object, no retain cycle }; [myobj callblock]; [myobj release]; } return self; } @end int main() { ObjectCallingBlockCallingObject *myObj = [[ObjectCallingBlockCallingObject alloc] init]; [myObj release]; return 0; } 

    Rappelez-vous également que les cycles de rétention peuvent se produire si votre bloc se réfère à un autre object qui conserve alors lui- self .

    Je ne suis pas sûr que la collecte des ordures puisse aider dans ces cycles de conservation. Si l’object conservant le bloc (que j’appellerai l’object serveur) survit à lui- self (l’object client), la référence à self à l’intérieur du bloc ne sera pas considérée comme cyclique tant que l’object retenu ne sera pas libéré. Si l’object serveur dépasse de loin ses clients, vous risquez de subir une fuite de mémoire importante.

    Comme il n’y a pas de solutions propres, je vous recommande les solutions suivantes. N’hésitez pas à choisir un ou plusieurs d’entre eux pour résoudre votre problème.

    • Utilisez des blocs uniquement pour l’ achèvement et non pour des événements ouverts. Par exemple, utilisez des blocs pour des méthodes comme doSomethingAndWhenDoneExecuteThisBlock: et non des méthodes telles que setNotificationHandlerBlock: Les blocs utilisés pour l’achèvement ont une fin de vie définie et doivent être libérés par les objects du serveur après leur évaluation. Cela empêche le cycle de rétention de vivre trop longtemps, même si cela se produit.
    • Faites cette danse de référence faible que vous avez décrite.
    • Fournir une méthode pour nettoyer votre object avant sa publication, qui “déconnecte” l’object des objects du serveur pouvant contenir des références à celui-ci; et appelez cette méthode avant d’appeler release sur l’object. Bien que cette méthode soit parfaitement correcte si votre object ne possède qu’un seul client (ou est un singleton dans un certain contexte), mais ne fonctionnera pas s’il a plusieurs clients. Vous êtes en train de vaincre le mécanisme de rétention des employés ici; c’est comme appeler dealloc au lieu de release .

    Si vous écrivez un object serveur, ne prenez les arguments de blocage que pour terminer. N’acceptez pas les arguments de bloc pour les rappels, tels que setEventHandlerBlock: Au lieu de cela, revenez au modèle de délégué classique: créez un protocole formel et setEventDelegate: une méthode setEventDelegate: Ne retenez pas le délégué. Si vous ne souhaitez même pas créer un protocole formel, acceptez un sélecteur en tant que rappel de délégué.

    Et enfin, ce modèle devrait sonner les alarmes:

     - (void) dealloc {
         [myServerObject releaseCallbackBlocksForObject: self];
         ...
     }
    

    Si vous essayez de décrocher des blocs qui peuvent se rapporter à vous-même à self intérieur de dealloc , vous êtes déjà en difficulté. dealloc ne peut jamais être appelé en raison du cycle de conservation provoqué par des références dans le bloc, ce qui signifie que votre object va simplement fuir jusqu’à ce que l’object serveur soit libéré.

    __block __unsafe_unretained modificateurs __block __unsafe_unretained proposés dans le message de Kevin peuvent provoquer une exception d’access incorrect en cas d’exécution d’un bloc dans un thread différent. Il vaut mieux utiliser uniquement le modificateur __block pour la variable temp et le rendre nul après l’utilisation.

     __block SomeType* this = self; [someObject messageWithBlock:^{ [this doSomething]; // here would be BAD_ACCESS in case of __unsafe_unretained with // multithreading and self was already released this = nil; }]; 

    Vous pouvez utiliser la bibliothèque libextobjc. Il est très populaire, il est utilisé dans ReactiveCocoa par exemple. https://github.com/jspahrsummers/libextobjc

    Il fournit 2 macros @weakify et @strongify, vous pouvez donc avoir:

     @weakify(self) [someObject messageWithBlock:^{ @strongify(self) [self doSomething]; }]; 

    Cela évite une référence directe forte afin de ne pas entrer dans un cycle de conservation de soi. Et aussi, cela empêche de devenir nul à mi-chemin, mais décrémente toujours correctement le nombre de retenues. Plus dans ce lien: http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html

    Que dis-tu de ça?

     - (void) foo { __weak __block me = self; myBlock = ^ { [[me someProp] someMessage]; } ... } 

    Je ne reçois plus l’avertissement du compilateur.

    Bloquer: un cycle de conservation se produit car il contient un bloc référencé dans le bloc; Si vous faites la copie de bloc et utilisez une variable membre, self conservera.