Conserver le cycle sur «soi» avec des blocs


167

J'ai bien peur que cette question soit assez basique, mais je pense qu'elle est pertinente pour beaucoup de programmeurs Objective-C qui se retrouvent dans des blocages.

Ce que j'ai entendu, c'est que puisque les blocs capturent les variables locales référencées en leur sein en tant que constcopies, l'utilisation selfdans un bloc peut entraîner un cycle de rétention, si ce bloc est copié. Donc, nous sommes censés utiliser __blockpour forcer le bloc à traiter directement au selflieu 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 est le suivant: si c'est vrai, y a-t-il un moyen d'éviter la laideur (en dehors de l'utilisation de GC)?


3
J'aime appeler mes selfmandataires thisjuste pour inverser les choses. En JavaScript, j'appelle mes thisfermetures self, donc c'est agréable et équilibré. :)
devios1

Je me demande si une action équivalente doit être effectuée si j'utilise des blocs Swift
Ben Lu

@BenLu absolument! dans les fermetures Swift (et les fonctions qui sont transmises et qui mentionnent le soi de manière implicite ou explicite) se conserveront. Parfois, cela est souhaité, et d'autres fois, cela crée un cycle (parce que la fermeture elle-même appartient à soi-même (ou appartient à quelque chose qui lui appartient). La principale raison pour laquelle cela se produit est à cause de l'ARC.
Ethan

1
Pour éviter les problèmes, la manière appropriée de définir «self» à utiliser dans un bloc est «__typeof (self) __weak lowSelf = self;» afin d'avoir une référence faible.
XLE_22

Réponses:


169

Strictement parlant, le fait qu'il s'agisse d'une copie const n'a rien à voir avec ce problème. Les blocs conserveront 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 copie de const est identique à la solution de contournement pour le problème de conservation; à savoir, en utilisant la __blockclasse de stockage pour la variable.

Dans tous les cas, pour répondre à votre question, il n'y a pas de véritable alternative ici. Si vous concevez votre propre API basée sur des blocs, et que cela a du sens de le faire, vous pouvez faire passer la valeur de selfin en tant qu'argument. Malheureusement, cela n'a pas de sens pour la plupart des API.

Veuillez noter que le référencement d'un ivar pose 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 bself->ivar.


Addendum: lors de la compilation en tant qu'ARC, les __blockinterruptions de cycle de conservation ne sont plus. Si vous compilez pour ARC, vous devez utiliser __weakou à la __unsafe_unretainedplace.


Aucun problème! Si cela répond à votre question de manière satisfaisante, je vous serais reconnaissant de bien vouloir la choisir comme réponse correcte à votre question. Sinon, faites-moi savoir comment je peux mieux répondre à votre question.
Lily Ballard

4
Pas de problème, Kevin. Donc, vous retardez immédiatement de sélectionner une réponse à une question, alors j'ai dû revenir un peu plus tard. À votre santé.
Jonathan Sterling

__unsafe_unretained id bself = self;
caleb

4
@JKLaiho: Bien sûr, ça __weakva aussi. Si vous savez pertinemment que l'objet ne peut pas être hors de portée lorsque le bloc est appelé, alors il __unsafe_unretainedest un peu plus rapide, mais en général, cela ne fera aucune différence. Si vous l'utilisez __weak, assurez-vous de le jeter dans une __strongvariable locale et de tester cela pour non- nilavant de faire quoi que ce soit avec.
Lily Ballard

2
@Rpranata: Oui. __blockL'effet secondaire de ne pas retenir et de libérer était purement dû à l'incapacité de raisonner correctement à ce sujet. Avec ARC, le compilateur a acquis cette capacité, et donc __blockmaintenant conserve et libère. Si vous devez éviter cela, vous devez utiliser __unsafe_unretained, qui indique au compilateur de ne pas effectuer de retenues ou de versions sur la valeur de la variable.
Lily Ballard

66

Utilisez simplement:

__weak id weakSelf = self;

[someObject someMethodWithBlock:^{
    [weakSelf someOtherMethod];
}];

Pour plus d'informations: WWDC 2011 - Blocks and 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;

2
Et l'avez-vous trouvé par hasard :)?
Tieme

2
Vous pouvez voir la vidéo ici - developer.apple.com/videos/wwdc/2011/…
nanjunda

Êtes-vous capable de vous référencer dans "someOtherMethod"? Est-ce que le moi ferait référence à lui-même faible à ce stade ou cela créerait-il également un cycle de rétention?
Oren

Salut @Oren, si vous essayez de vous référencer dans "someOtherMethod", vous obtiendrez un avertissement Xcode. Mon approche fait juste une référence faible de soi.
3lvis

1
Je n'ai reçu un avertissement qu'en me référant directement à l'intérieur du bloc. Se mettre à l'intérieur de someOtherMethod n'a provoqué aucun avertissement. Est-ce parce que xcode n'est pas assez intelligent ou n'est-ce pas un problème? Est-ce que le fait de se référer à soi dans someOtherMethod ferait déjà référence à lowSelf puisque c'est sur cela que vous appelez la méthode?
Oren

22

Cela peut être évident, mais vous n'avez à faire le vilain selfalias que lorsque vous savez que vous obtiendrez un cycle de rétention. Si le bloc est juste une chose ponctuelle, je pense que vous pouvez ignorer en toute sécurité la conservation self. Le mauvais cas 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 superclasse, par exemple. Nous conservons le gestionnaire de tampon, le gestionnaire de tampon nous retient. 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 d' selfalias. Vous obtenez un cycle de conservation, mais l'opération est de courte durée et le bloc finira par sortir de la mémoire, interrompant le cycle. Mais mon expérience avec les blocs est très petite et il se peut que l' selfaliasing soit une meilleure pratique à long terme.


6
Bon point. Ce n'est qu'un cycle de rétention si soi-même maintient le bloc en vie. Dans le cas de blocs qui ne sont jamais copiés, ou de blocs avec une durée limitée garantie (par exemple le bloc de complétion pour une animation UIView), vous n'avez pas à vous en préoccuper.
Lily Ballard

6
En principe, vous avez raison. Cependant, si vous exécutiez le code de l'exemple, vous planteriez. Les propriétés de bloc doivent toujours être déclarées comme copynon retain. S'ils sont juste retain, il n'y a aucune garantie qu'ils seront déplacés hors de la pile, ce qui signifie que lorsque vous allez l'exécuter, il ne sera plus là. (et la copie et le bloc déjà copié sont optimisés pour une conservation)
Dave DeLong

Ah, bien sûr, une faute de frappe. J'ai traversé la retainphase il y a quelque temps et j'ai rapidement réalisé ce que vous dites :) Merci!
zoul

Je suis à peu près sûr qu'il retainest complètement ignoré pour les blocs (sauf s'ils ont déjà quitté la pile avec copy).
Steven Fisher

@Dave DeLong, Non, cela ne planterait pas car la propriété @property (conserver) est utilisée uniquement pour une référence d'objet, pas pour le bloc .. Il n'y a pas du tout besoin d'une copie à utiliser ici ..
DeniziOS

20

Publier 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 auto-référence à l'intérieur d'un bloc. Ce n'est pas le cas, ce n'est que lorsque l'objet lui-même contient un bloc. Et en fait, si vous utilisez blockSelf dans ces cas, l'objet peut être libéré avant que vous ne récupériez le résultat du bloc, puis il plantera lorsqu'il essaiera de l'appeler, donc clairement vous voulez que le soi soit conservé jusqu'à la réponse revient.

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

#import <Foundation/Foundation.h>

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'objet appelant ne contient pas de bloc qui provoquera un cycle de rétention lorsque vous référencez self:

#import <Foundation/Foundation.h>

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;
} 

C'est une idée fausse très répandue et peut être dangereux, car les blocs qui devraient retenir selfpeut pas due aux personnes de plus-appliquer ce correctif. Ceci est un bon exemple pour éviter les cycles de rétention dans le code non ARC, merci pour la publication.
Carl Veazey

9

Rappelez-vous également que des cycles de rétention peuvent se produire si votre bloc se réfère à un autre objet qui retient ensuite self.

Je ne suis pas sûr que Garbage Collection puisse aider dans ces cycles de conservation. Si l'objet conservant le bloc (que j'appellerai l'objet serveur) survit self(l'objet client), la référence à l' selfintérieur du bloc ne sera pas considérée comme cyclique jusqu'à ce que l'objet de rétention lui-même soit libéré. Si l'objet serveur survit de loin à ses clients, vous pouvez avoir une fuite de mémoire importante.

Puisqu'il n'y a pas de solutions propres, je recommanderais les solutions de contournement suivantes. N'hésitez pas à en choisir un ou plusieurs pour résoudre votre problème.

  • Utilisez des blocs uniquement pour la finalisation , et non pour les événements ouverts. Par exemple, utilisez des blocs pour des méthodes comme doSomethingAndWhenDoneExecuteThisBlock:, et non des méthodes commesetNotificationHandlerBlock: . Les blocs utilisés pour l'achèvement ont une fin de vie définie et doivent être libérés par les objets serveur après leur évaluation. Cela empêche le cycle de rétention de vivre trop longtemps même s'il se produit.
  • Faites cette danse de référence faible que vous avez décrite.
  • Fournissez une méthode pour nettoyer votre objet avant sa sortie, qui "déconnecte" l'objet des objets serveur qui peuvent lui contenir des références; et appelez cette méthode avant d'appeler release sur l'objet. Bien que cette méthode soit parfaitement adaptée si votre objet n'a qu'un seul client (ou est un singleton dans un certain contexte), elle s'effondrera s'il a plusieurs clients. Vous êtes en train de vaincre le mécanisme de comptage de retenue ici; cela revient à appeler deallocau lieu de release.

Si vous écrivez un objet serveur, ne prenez les arguments de bloc 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 publiez une setEventDelegate:méthode. Ne retenez pas le délégué. Si vous ne souhaitez même pas créer de protocole formel, acceptez un sélecteur comme rappel de délégué.

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

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

Si vous essayez de décrocher des blocs qui peuvent faire référence selfde l'intérieur dealloc, vous avez déjà des problèmes. deallocpeut ne jamais être appelé en raison du cycle de rétention causé par les références dans le bloc, ce qui signifie que votre objet va simplement fuir jusqu'à ce que l'objet serveur soit désalloué.


GC aide si vous utilisez __weakcorrectement.
tc.

Le suivi de la collecte des ordures peut bien sûr gérer les cycles de rétention. La conservation des cycles n'est un problème que pour les environnements de comptage de références
newacct

Juste pour que tout le monde le sache, le garbage collection a été déconseillé dans OS X v10.8 au profit du comptage automatique des références (ARC) et devrait être supprimé dans une future version d'OS X ( developer.apple.com/library/mac/#releasenotes / ObjectiveC /… ).
Ricardo Sanchez-Saez

1

__block __unsafe_unretainedles modificateurs suggérés dans l'article de Kevin peuvent provoquer l'exception d'accès incorrect en cas de blocage exécuté dans un thread différent. Il est préférable d'utiliser uniquement le modificateur __block pour la variable temporaire et de 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;
}];

Ne serait-il pas vraiment plus sûr d'utiliser simplement __weak au lieu de __block pour éviter d'avoir à supprimer la variable après l'avoir utilisée? Je veux dire, cette solution est excellente si vous voulez interrompre d'autres types de cycles, mais je ne vois certainement pas d'avantages particuliers pour les cycles «auto» conservés.
ale0xB

Vous ne pouvez pas utiliser __weak si votre plate-forme cible est iOS 4.x. Parfois aussi, vous avez besoin que le code du bloc ait été exécuté pour l'objet valide, pas pour nil.
b1gbr0

1

Vous pouvez utiliser la bibliothèque libextobjc. Il est assez 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 empêche une référence forte directe afin de ne pas entrer dans un cycle de rétention de soi. Et aussi, cela empêche le soi de devenir nul à mi-chemin, mais décrémente toujours correctement le nombre de rétention. Plus d'informations sur ce lien: http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html


1
Avant de montrer le code simplifié, il serait préférable de savoir ce qui se cache derrière, tout le monde devrait connaître les deux vraies lignes de code.
Alex Cio

0

Que dis-tu de ça?

- (void) foo {
     __weak __block me = self;

     myBlock = ^ {
        [[me someProp] someMessage];
     }
     ...
 }

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


-1

Bloc: un cycle de rétention se produira car il contient un bloc référencé dans le bloc; Si vous effectuez la copie de bloc et utilisez une variable membre, self sera conservé.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.