web-dev-qa-db-fra.com

Conserver le cycle sur soi avec des blocs

Je crains que cette question ne soit assez fondamentale, mais je pense que cela concerne un grand nombre de programmeurs Objective-C qui se lancent dans des blocages.

Ce que j'ai entendu dire, 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 sommes 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 cela est vrai, y a-t-il un moyen d'éviter la laideur (en dehors de l'utilisation de GC)?

165
Jonathan Sterling

Strictement parlant, le fait qu'il s'agisse d'une copie conforme 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 const-copy est identique à la solution de contournement pour le problème de conservation; à savoir, en utilisant le __block classe de stockage pour la variable.

En tout cas, pour répondre à votre question, il n’ya pas de réelle alternative ici. Si vous concevez votre propre API basée sur des blocs et qu'il est logique de le faire, vous pouvez également faire en sorte que le bloc reçoive la valeur de self en argument. Malheureusement, cela n’a aucun 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 une propriété à la place ou utilisez bself->ivar.


Addendum: lors de la compilation en tant qu'ARC, __block ne rompt plus les cycles de conservation. Si vous compilez pour ARC, vous devez utiliser __weak ou __unsafe_unretained au lieu.

168
Lily Ballard

Il suffit d'utiliser:

__weak id weakSelf = self;

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

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

Cela peut sembler évident, mais vous n'avez qu'à faire le vilain alias self lorsque vous savez que vous obtiendrez un cycle de conservation. Si le blocage n’est qu’une action ponctuelle, je pense que vous pouvez ignorer en toute sécurité la conservation sur 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 super-classe, par exemple. Nous conservons le gestionnaire de mémoire tampon, le gestionnaire de mémoire tampon 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 pseudo self. Vous obtenez un cycle de conservation, mais l'opération est de courte durée et le bloc finira par manquer de mémoire, rompant ainsi le cycle. Mais mon expérience avec les blocs est très petite et il se peut que self le repliement du spectre soit une meilleure pratique à long terme.

22
zoul

Publier une autre réponse car c’était aussi un problème pour moi. Au départ, je pensais que je devais utiliser blockSelf partout où il y avait une référence propre à l'intérieur d'un bloc. Ce n'est pas le cas, ce n'est que lorsque l'objet lui-même est bloqué. Et en fait, si vous utilisez blockSelf dans ces cas, l’objet peut être désalloué avant que le résultat ne soit renvoyé du bloc, puis il se bloque lorsqu’il essaie de l’appeler. revient.

Le premier cas montre quand un cycle de conservation va se produire 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 provoquerait un cycle de conservation 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;
} 
19
possen

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

Je ne suis pas sûr que Garbage Collection puisse vous aider dans ces cycles de conservation. Si l'objet qui conserve le bloc (que j'appellerai l'objet serveur) survivra à self (l'objet client), la référence à self à l'intérieur du bloc ne sera pas considérée comme cyclique tant que l'objet ne sera pas conservé. lui-même est libéré. Si l'objet serveur survit de loin à ses clients, vous risquez d'avoir une fuite de mémoire importante.

Puisqu'il n'y a pas de solution propre, je recommanderais 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 achèvement, et pas pour les événements ouverts. Par exemple, utilisez des blocs pour des méthodes telles que doSomethingAndWhenDoneExecuteThisBlock:, et non des méthodes comme setNotificationHandlerBlock:. 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 conservation de vivre trop longtemps, même s'il se produit.
  • Faites cette danse de faible référence que vous avez décrite.
  • Fournissez une méthode pour nettoyer votre objet avant sa publication, ce qui "déconnecte" l'objet des objets serveur susceptibles de contenir des références; et appelez cette méthode avant d'appeler release sur l'objet. Alors que cette méthode convient parfaitement si votre objet n'a qu'un seul client (ou est un singleton dans un contexte donné), il échouera s'il comporte plusieurs clients. Vous défaites essentiellement le mécanisme de comptabilisation des retenues ici; c'est comme appeler dealloc au lieu de release.

Si vous écrivez un objet serveur, prenez les arguments de blocage uniquement. Ne pas accepter les arguments de bloc pour les rappels, tels que setEventHandlerBlock:. Utilisez plutôt le modèle de délégué classique: créez un protocole formel et publiez un message setEventDelegate: méthode. Ne conservez pas le délégué. Si vous ne voulez même pas créer de 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 {
 [release de MonServerObjectCallbackBlocksForObject: self]; 
 ... 
} 

Si vous essayez de décrocher des blocs pouvant faire référence à self à partir de dealloc, vous avez déjà un problème. dealloc peut ne jamais être appelé en raison du cycle de conservation provoqué par les références dans le bloc, ce qui signifie que votre objet va tout simplement fuir jusqu'à ce que l'objet serveur soit désalloué.

9
Dave R

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 pour que vous puissiez avoir:

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

Cela évite une référence forte et directe afin d'éviter un cycle de rétention. Et aussi, il empêche le moi de devenir nul à mi-chemin, mais décroît 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

1
Yuri Solodkin

__block __unsafe_unretained Les modificateurs suggérés dans le message de Kevin peuvent entraîner une exception d’accès incorrect en cas de blocage d’un bloc exécuté dans un autre thread. Il vaut mieux utiliser uniquement __ block le modificateur de 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;
}];
1
b1gbr0

Que dis-tu de ça?

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

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

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

0
Καrτhικ