166 votes

Conserver le cycle sur « soi » avec des blocs

Je crains que cette question est assez basique, mais je pense que c'est important pour beaucoup d'Objective-C programmeurs qui sont dans des blocs.

Ce que j'ai entendu, c'est que, depuis les blocs de capture de variables locales référencées à l'intérieur comme const des copies, à l'aide de self dans un bloc peut entraîner dans un cycle de conserver, si ce bloc est copié. Donc, nous sommes censés utiliser __block de la force du bloc à traiter directement avec l' self , au lieu de l'avoir copié.

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

au lieu de simplement

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

Ce que je voudrais savoir est la suivante: si cela est vrai, est-il une manière que je peux éviter la laideur (à part en utilisant GC)?

168voto

Kevin Ballard Points 88866

Strictement parlant, le fait que c'est un const copie n'a rien à voir avec ce problème. Blocs de conserver toute l'obj-c les valeurs qui sont capturés alors qu'ils sont créés. Il se trouve que la solution de contournement pour la const-copie est identique à la solution de contournement pour le conserver question; à savoir, à l'aide de l' __block classe de stockage de la variable.

En tout cas, pour répondre à votre question, il n'y a pas de véritable alternative. Si vous êtes à la conception de votre propre bloc de base de l'API, et il est logique de le faire, vous pourriez avoir le bloc se passer la valeur de self comme un argument. Malheureusement, cela ne fait 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 faire référence à un ivar dans votre bloc, soit l'utilisation d'une propriété au lieu ou à l'utilisation bself->ivar.


Addendum: Lors de la compilation de l'ARC, __block de ne plus conserver les sauts de cycles. Si vous êtes à la compilation de l'ARC, vous devez utiliser __weak ou __unsafe_unretained à la place.

65voto

NSElvis Points 3083

Il suffit d’utiliser :

Pour plus d’informations : WWDC 2011 - blocs et Grand Central Dispatch dans la pratique.

https://developer.Apple.com/Videos/WWDC/2011/?ID=308

Remarque : si cela ne fonctionne pas vous pouvez essayer

22voto

zoul Points 51637

Cela peut être évident, mais vous n'avez qu'à faire le vilain self alias lorsque vous savez que vous aurez un cycle de conserver. Si le bloc est juste un one-shot chose alors je pense que vous pouvez en toute sécurité ignorer les conserver en self. La mauvaise, c'est quand vous avez le bloc comme une 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 il serait logique lors de la communication avec une super-classe, par exemple. Nous nous réservons le tampon gestionnaire, le tampon gestionnaire conserve nous. Comparer 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 l' self aliasing. Vous obtenez un cycle de conserver, mais l'opération est de courte durée et le bloc de sortir de la mémoire finit par briser le cycle. Mais mon expérience avec des blocs est très petit et il se pourrait qu' self aliasing vient de sortir en tant que meilleure pratique à la longue.

19voto

possen Points 595

Poster une autre réponse, parce que c'était un problème pour moi aussi. J'ai d'abord pensé que je devais utiliser blockSelf n'importe où il y a un soi de référence à l'intérieur d'un bloc. Ce n'est pas le cas, c'est seulement lorsque l'objet lui-même a un bloc. Et en fait, si vous utilisez blockSelf dans ces cas, l'objet peut obtenir dealloc avais avant d'obtenir le résultat en arrière du bloc et ensuite il va se planter quand il essaie de l'appeler, ce qui indique clairement que vous voulez de soi à être conservés jusqu'à ce que la réponse est de retour.

Premier cas démontre quand un conserver cycle va se produire, car il contient un bloc qui est 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 blockSelf dans le second cas, parce que l'objet appelant ne dispose pas d'un bloc, dans ce qui va provoquer un cycle de conserver lorsque vous faites référence à soi-même:

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

9voto

Dave R Points 535

Rappelez-vous aussi que de conserver les cycles peuvent se produire si votre bloc fait référence à un autre objet qui conserve alors self.

Je ne suis pas sûr que la Collecte des Ordures peut aider à conserver ces cycles. Si l'objet en conservant le bloc (que je vais appeler le serveur de l'objet) survit self (le client de l'objet), la référence à l' self à l'intérieur du bloc ne sera pas considéré comme cyclique jusqu'à ce que le maintien de l'objet lui-même est libéré. Si le serveur de l'objet de la mesure survit à ses clients, vous pouvez avoir une fuite de mémoire importante.

Puisqu'il n'existe pas de solutions propres, je vous recommande les solutions de contournement suivantes. N'hésitez pas à choisir l'un ou plusieurs d'entre eux afin de résoudre votre problème.

  • L'utilisation de blocs seulement pour l'achèvement, et pas pour ouvert événements. Par exemple, l'utilisation de blocs de méthodes comme l' doSomethingAndWhenDoneExecuteThisBlock:, et non pas des méthodes comme setNotificationHandlerBlock:. Les blocs utilisés pour la réalisation ont des extrémités de la vie, et il doit être libéré par le serveur, les objets une fois qu'ils sont évalués. Cela empêche le conserver cycle de vie depuis trop longtemps, même si elle se produit.
  • Faire que la faiblesse de référence de la danse que vous avez décrit.
  • Fournir une méthode pour nettoyer votre objet avant qu'il soit libéré, qui "déconnecte" de l'objet à partir du serveur d'objets qui peuvent contenir des références à elle; et cette méthode est appelée avant l'appel à la libération de l'objet. Bien que cette méthode est parfaitement bien si votre objet n'a qu'un seul client (ou est un singleton dans un certain contexte), mais briser si l'on a plusieurs clients. Vous êtes essentiellement en battant le conserver-mécanisme de comptage ici; cela ressemble à l'appel dealloc au lieu de release.

Si vous êtes à la rédaction d'un objet serveur, prendre en bloc les arguments que pour l'achèvement. Ne pas accepter en bloc les arguments pour les rappels, comme setEventHandlerBlock:. Au lieu de cela, revenir à la classique modèle délégué: créer un protocole formel, et de la publicité d'un setEventDelegate: méthode. Ne pas conserver le délégué. Si vous ne voulez même pas à créer un protocole formel, accepter un sélecteur en tant que délégué de rappel.

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

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

Si vous êtes en essayant de détacher les blocs qui peuvent se référer à des self depuis l'intérieur d' dealloc, vous êtes déjà en difficulté. dealloc ne peut jamais être appelé en raison de la conserver cycle causés par des références dans le bloc, ce qui signifie que votre objet est simplement de fuite jusqu'à ce que l'objet serveur est libéré.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X