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.
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. 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.