Toujours passer une référence faible de soi en bloc dans ARC?


252

Je suis un peu confus quant à l'utilisation des blocs dans Objective-C. J'utilise actuellement ARC et j'ai beaucoup de blocs dans mon application, faisant toujours référence à la selfplace de sa référence faible. Cela peut-il être la raison pour laquelle ces blocs le retiennent selfet l'empêchent d'être désalloué? La question est, dois-je toujours utiliser une weakréférence de selfdans un bloc?

-(void)handleNewerData:(NSArray *)arr
{
    ProcessOperation *operation =
    [[ProcessOperation alloc] initWithDataToProcess:arr
                                         completion:^(NSMutableArray *rows) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self updateFeed:arr rows:rows];
        });
    }];
    [dataProcessQueue addOperation:operation];
}

ProcessOperation.h

@interface ProcessOperation : NSOperation
{
    NSMutableArray *dataArr;
    NSMutableArray *rowHeightsArr;
    void (^callback)(NSMutableArray *rows);
}

ProcessOperation.m

-(id)initWithDataToProcess:(NSArray *)data completion:(void (^)(NSMutableArray *rows))cb{

    if(self =[super init]){
        dataArr = [NSMutableArray arrayWithArray:data];
        rowHeightsArr = [NSMutableArray new];
        callback = cb;
    }
    return self;
}

- (void)main {
    @autoreleasepool {
        ...
        callback(rowHeightsArr);
    }
}

Si vous voulez un discours approfondi sur ce sujet, lisez dhoerl.wordpress.com/2013/04/23/…
David H

Réponses:


721

Cela aide à ne pas se concentrer sur la partie strongou la weakpartie de la discussion. Concentrez-vous plutôt sur la partie cycle .

Un cycle de rétention est une boucle qui se produit lorsque l’objet A conserve l’objet B et que l’ objet B conserve l’objet A. Dans ce cas, si l’un ou l’autre des objets est libéré:

  • L'objet A ne sera pas désalloué car l'objet B en contient une référence.
  • Mais l'objet B ne sera jamais désalloué tant que l'objet A y aura une référence.
  • Mais l'objet A ne sera jamais désalloué car l'objet B en contient une référence.
  • À l'infini

Ainsi, ces deux objets resteront simplement en mémoire pendant toute la durée de vie du programme, même s'ils doivent, si tout fonctionnait correctement, être désalloués.

Donc, ce qui nous inquiète, c'est de conserver les cycles , et il n'y a rien sur les blocs en soi qui créent ces cycles. Ce n'est pas un problème, par exemple:

[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
   [self doSomethingWithObject:obj];
}];

Le bloc conserve self, mais selfne conserve pas le bloc. Si l'un ou l'autre est libéré, aucun cycle n'est créé et tout est désalloué comme il se doit.

Là où vous avez des ennuis, c'est quelque chose comme:

//In the interface:
@property (strong) void(^myBlock)(id obj, NSUInteger idx, BOOL *stop);

//In the implementation:
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [self doSomethingWithObj:obj];     
}];

Maintenant, votre objet ( self) a une strongréférence explicite au bloc. Et le bloc a une référence forte implicite à self. C'est un cycle, et maintenant aucun objet ne sera désalloué correctement.

Parce que, dans une situation comme celle-ci, self par définition a déjà une strongréférence au bloc, il est généralement plus facile à résoudre en faisant une référence explicitement faible au selfbloc à utiliser:

__weak MyObject *weakSelf = self;
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [weakSelf doSomethingWithObj:obj];     
}];

Mais cela ne devrait pas être le modèle par défaut que vous suivez lorsque vous traitez avec des blocs qui appellent self! Cela ne doit être utilisé que pour rompre ce qui serait autrement un cycle de rétention entre soi et le bloc. Si vous deviez adopter ce modèle partout, vous courriez le risque de passer un bloc à quelque chose qui a été exécuté après avoir selfété désalloué.

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not retained!
  [weakSelf doSomething];
}];

2
Je ne suis pas sûr que A retienne B, B retienne A fera un cycle infini. Du point de vue du compte de référence, le compte de référence de A et B est 1. Ce qui cause un cycle de rétention pour cette situation est quand il n'y a aucun autre groupe ayant une référence forte de A et B à l'extérieur - cela signifie que nous ne pouvons pas atteindre ces deux objets (nous ne peut pas contrôler A pour libérer B et vice versa), à cet effet, A et B se réfèrent mutuellement pour se maintenir tous les deux en vie.
Danyun Liu

@Danyun S'il est vrai qu'un cycle de conservation entre A et B n'est pas irrécupérable tant que toutes les autres références à ces objets n'ont pas été publiées, cela n'en fait pas moins un cycle. Inversement, ce n'est pas parce qu'un cycle particulier peut être récupérable qu'il est acceptable de l'avoir dans votre code. Les cycles de conservation sont une odeur de mauvaise conception.
jemmons

@jemmons Oui, nous devons toujours éviter autant que possible la conception d'un cycle de rétention.
Danyun Liu

1
@Master Il m'est impossible de le dire. Cela dépend complètement de la mise en œuvre de votre -setCompleteionBlockWithSuccess:failure:méthode. Mais si paginatorest détenu par ViewController, et que ces blocs ne sont pas appelés après ViewControllerêtre libérés, l'utilisation d'une __weakréférence serait le mouvement sûr (car il selfpossède la chose qui possède les blocs, et il est donc probable qu'il soit toujours là lorsque les blocs l'appellent même s'ils ne le conservent pas). Mais cela fait beaucoup de «si». Cela dépend vraiment de ce que cela est censé faire.
jemmons

1
@Jai Non, et c'est au cœur du problème de gestion de la mémoire avec les blocs / fermetures. Les objets sont désalloués lorsque rien ne leur appartient. MyObjectet les SomeOtherObjectdeux possèdent le bloc. Mais parce que la référence du bloc MyObjectest weak, le bloc n'est pas propriétaire MyObject. Ainsi, alors que le bloc est garanti d'exister aussi longtemps que l' un MyObject ou l' autreSomeOtherObject existe, il n'y a aucune garantie qui MyObjectexistera aussi longtemps que le bloc existera. MyObjectpeut être complètement désalloué et, tant SomeOtherObjectqu'il existe, le bloc sera toujours là.
jemmons

26

Vous n'êtes pas obligé de toujours utiliser une référence faible. Si votre bloc n'est pas conservé, mais exécuté puis rejeté, vous pouvez vous capturer fortement, car cela ne créera pas de cycle de conservation. Dans certains cas, vous souhaitez même que le bloc se maintienne jusqu'à la fin du bloc afin qu'il ne se désalloue pas prématurément. Cependant, si vous capturez fortement le bloc et que vous vous capturez à l'intérieur, cela créera un cycle de rétention.


Eh bien, je viens d'exécuter le bloc en tant que rappel et je ne voudrais pas du tout que self soit désalloué. Mais il me semble que je créais des cycles de rétention car le View Controller en question n'est pas désalloué ...
the_critic

1
Par exemple, si vous avez un élément de bouton de barre qui est conservé par le contrôleur de vue et que vous vous capturez fortement dans ce bloc, il y aura un cycle de conservation.
Leo Natan

1
@MartinE. Vous devez simplement mettre à jour votre question avec des exemples de code. Leo a tout à fait raison (+1), cela ne provoque pas nécessairement un cycle de référence fort, mais cela peut, selon la façon dont vous utilisez ces blocs. Il nous sera plus facile de vous aider si vous fournissez des extraits de code.
Rob

@LeoNatan Je comprends la notion de cycles de rétention, mais je ne suis pas sûr de ce qui se passe dans les blocs, ce qui
m'embrouille

Dans le code ci-dessus, votre auto-instance sera libérée une fois l'opération terminée et le bloc libéré. Vous devriez lire comment les blocs fonctionnent et quoi et quand ils capturent dans leur portée.
Leo Natan

26

Je suis totalement d'accord avec @jemmons:

Mais cela ne devrait pas être le modèle par défaut que vous suivez lorsque vous traitez avec des blocs qui s'appellent eux-mêmes! Cela ne doit être utilisé que pour rompre ce qui serait autrement un cycle de rétention entre soi et le bloc. Si vous deviez adopter ce modèle partout, vous courriez le risque de passer un bloc à quelque chose qui a été exécuté après que self ait été désalloué.

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not  retained!
  [weakSelf doSomething];
}];

Pour surmonter ce problème, on peut définir une référence forte sur l' weakSelfintérieur du bloc:

__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  MyObject *strongSelf = weakSelf;
  [strongSelf doSomething];
}];

3
StrongSelf n'augmenterait-il pas le nombre de références de faiblesSelf? Créer ainsi un cycle de rétention?
mskw

12
Les cycles de conservation n'ont d'importance que s'ils existent dans l'état statique de l'objet. Pendant que le code s'exécute et que son état est en évolution, les retenues multiples et éventuellement redondantes conviennent. Quoi qu'il en soit, en ce qui concerne ce modèle, la capture d'une référence forte ne fait rien pour le cas où l'auto se désalloue avant l'exécution du bloc, cela peut toujours se produire. Cela garantit que self ne sera pas désalloué lors de l'exécution du bloc. Cela importe si le bloc effectue lui-même des opérations asynchrones, ce qui donne une fenêtre pour que cela se produise.
Pierre Houston

@smallduck, votre explication est excellente. Maintenant, je comprends mieux cela. Les livres ne couvraient pas cela, merci.
Huibin Zhang

5
Ce n'est pas un bon exemple de strongSelf, car l'ajout explicite de strongSelf est exactement ce que le runtime ferait de toute façon: sur la ligne doSomething, une référence forte est prise pour la durée de l'appel de méthode. Si faiblesseSelf a déjà été invalidé, la référence forte est nulle et l'appel de méthode est un no-op. StrongSelf vous aide si vous avez une série d'opérations ou si vous accédez à un champ membre ( ->), où vous voulez garantir que vous avez réellement obtenu une référence valide et la conserver en continu sur l'ensemble des opérations, par exempleif ( strongSelf ) { /* several operations */ }
Ethan

19

Comme Leo le fait remarquer, le code que vous avez ajouté à votre question ne suggère pas un cycle de référence solide (aka, cycle de conservation). Un problème lié à l'opération qui pourrait provoquer un cycle de référence fort serait si l'opération n'est pas libérée. Bien que votre extrait de code suggère que vous n'avez pas défini votre opération comme étant simultanée, mais si vous l'avez, elle ne sera pas publiée si vous n'avez jamais publié isFinished, ou si vous aviez des dépendances circulaires, ou quelque chose comme ça. Et si l'opération n'est pas validée, le contrôleur de vue ne le sera pas non plus. Je suggère d'ajouter un point d'arrêt ou NSLogdans la deallocméthode de votre opération et de confirmer que cela est appelé.

Tu as dit:

Je comprends la notion de cycles de rétention, mais je ne sais pas trop ce qui se passe dans les blocs, ce qui m'embrouille un peu

Les problèmes de cycle de rétention (cycle de référence fort) qui se produisent avec les blocs sont similaires aux problèmes de cycle de rétention que vous connaissez. Un bloc conservera des références fortes à tous les objets qui apparaissent dans le bloc, et il ne libérera pas ces références fortes jusqu'à ce que le bloc lui-même soit libéré. Ainsi, si le bloc fait référence self, ou même fait simplement référence à une variable d'instance de self, qui maintiendra une référence forte à soi, cela n'est pas résolu jusqu'à ce que le bloc soit libéré (ou dans ce cas, jusqu'à ce que la NSOperationsous-classe soit libérée.

Pour plus d'informations, consultez la section Évitez les cycles de référence forts lors de la capture de l'auto- section du document Programmation avec Objective-C: Travailler avec des blocs .

Si votre contrôleur de vue n'est toujours pas libéré, il vous suffit d'identifier où réside la référence forte non résolue (en supposant que vous avez confirmé que la NSOperationdésallocation est en cours). Un exemple courant est l'utilisation d'une répétition NSTimer. Ou un delegateobjet personnalisé ou autre qui maintient par erreur une strongréférence. Vous pouvez souvent utiliser des instruments pour localiser où les objets obtiennent leurs références fortes, par exemple:

enregistrer le nombre de références dans Xcode 6

Ou dans Xcode 5:

enregistrer le nombre de références dans Xcode 5


1
Un autre exemple serait si l'opération est conservée dans le créateur de bloc et n'est pas libérée une fois terminée. +1 sur la belle écriture!
Leo Natan

@LeoNatan D'accord, bien que l'extrait de code le représente comme une variable locale qui serait publiée s'il utilise ARC. Mais tu as tout à fait raison!
Rob

1
Oui, je ne faisais que donner un exemple, comme le PO l'a demandé dans l'autre réponse.
Leo Natan

Soit dit en passant, Xcode 8 a "Debug Memory Graph", qui est un moyen encore plus facile de trouver des références solides à des objets qui n'ont pas été publiés. Voir stackoverflow.com/questions/30992338/… .
Rob

0

Quelques explications ignorer une condition sur le cycle de conserver [Si un groupe d'objets est relié par un cercle de relations solides, ils se tiennent mutuellement en vie , même s'il n'y a pas de solides références à l' extérieur du groupe.] Pour plus d' informations, lire le document de


-2

Voici comment vous pouvez utiliser le self à l'intérieur du bloc:

// appel du bloc

 NSString *returnedText= checkIfOutsideMethodIsCalled(self);

NSString* (^checkIfOutsideMethodIsCalled)(*)=^NSString*(id obj)
{
             [obj MethodNameYouWantToCall]; // this is how it will call the object 
            return @"Called";


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