Pourquoi les délégués Objective-C reçoivent-ils généralement la propriété assignée au lieu de retenir?

176

Je surfe sur le merveilleux blog maintenu par Scott Stevenson, et j'essaie de comprendre un concept fondamental d'Objectif-C consistant à attribuer aux délégués la propriété «assigner» vs «retenir». Notez que les deux sont identiques dans un environnement garbage collection. Je suis principalement concerné par un environnement non basé sur GC (par exemple: iPhone).

Directement du blog de Scott:

"Le mot-clé assign générera un setter qui attribue directement la valeur à la variable d'instance, plutôt que de la copier ou de la conserver. C'est mieux pour les types primitifs comme NSInteger et CGFloat, ou les objets que vous ne possédez pas directement, tels que les délégués."

Qu'est-ce que cela signifie que vous ne possédez pas directement l'objet délégué? Je retiens généralement mes délégués, parce que si je ne veux pas qu'ils partent dans l'abîme, retenez s'en chargera pour moi. J'abstrait généralement UITableViewController de sa source de données respective et de son délégué également. Je conserve également cet objet particulier. Je veux m'assurer qu'il ne disparaît jamais afin que mon UITableView ait toujours son délégué.

Quelqu'un peut-il expliquer davantage où / pourquoi je me trompe, afin que je puisse comprendre ce paradigme courant dans la programmation Objective-C 2.0 d'utiliser la propriété assign sur les délégués au lieu de retenir?

Merci!

Pluménateur
la source
Redéfini avec "délégués" et sans "iphone".
Quinn Taylor
pourquoi le délégué est-il assigné au lieu de copier (comme NSString?)
OMGPOP

Réponses:

175

La raison pour laquelle vous évitez de conserver les délégués est que vous devez éviter un cycle de conservation:

A crée B A se définit comme le délégué de B… A est libéré par son propriétaire

Si B avait conservé A, A ne serait pas libéré, car B possède A, ainsi le dealloc de A ne serait jamais appelé, provoquant une fuite à la fois de A et de B.

Vous ne devriez pas vous inquiéter de la disparition de A car il possède B et s'en débarrasse ainsi dans dealloc.

Andrew Pouliot
la source
Je ne suis pas d'accord, Mike. Je viens de trouver un problème où un modal a un délégué qui rejette le modal. Mais quand je fais un avertissement de mémoire dans le modal, cela libère le délégué. Ensuite, quand je vais rejeter mon modal, le délégué est nul. Crash.
Paul Shapiro
Ok donc je ne suis pas d'accord, mais vous avez raison de dire que c'est un défaut de conception. J'ai découvert que je transmettais mon appel de licenciement à une classe d'enfants du vrai démonteur. Cette classe enfant a été libérée et n'a pas pu passer au délégué de conteneur du modal pour la rejeter. Je l'ai changé pour transmettre un pointeur au délégué final et celui-ci n'a pas été libéré à l'avertissement de mémoire et tout va bien.
Paul Shapiro
2
Votre code ne doit jamais être écrit de telle manière qu'un délégué nil le fasse planter. Seul l'objet propriétaire doit avoir une référence propriétaire. Lorsqu'elle est libérée, elle doit définir les délégués des objets possédés sur nil avant de les libérer. Ensuite, tous les messages envoyés au délégué nil sont simplement ignorés. Cependant, passer un objet nul dans un message peut planter. Assurez-vous simplement de ne pas traiter les délégués de cette façon.
David Gish
Attendez - n'est-ce pas ça weak? La question est de savoir pourquoi utiliser assignau lieu de weak?
wcochran
3
@wcochran: Non, cette question est de savoir pourquoi utiliser à la assignplace de retain. La question est plus ancienne que l'ARC; weaket strong(ce dernier étant synonyme de retain) n'existait pas avant l'introduction de l'ARC. Vous devriez poser votre question sur weakvs assignséparément.
Peter Hosey
44

Parce que l'objet qui envoie les messages de délégué ne possède pas le délégué.

Souvent, c'est l'inverse, comme lorsqu'un contrôleur se définit comme le délégué d'une vue ou d'une fenêtre: le contrôleur possède la vue / fenêtre, donc si la vue / fenêtre possédait son délégué, les deux objets se posséderaient. Ceci, bien sûr, est un cycle de rétention, similaire à une fuite avec la même conséquence (les objets qui devraient être morts restent vivants).

D'autres fois, les objets sont des pairs: ni l'un ni l'autre ne possède l'autre, probablement parce qu'ils appartiennent tous deux au même troisième objet.

Dans tous les cas, l'objet avec le délégué ne doit pas conserver son délégué.

(Il y a au moins une exception, au fait. Je ne me souviens pas de quoi il s'agissait, et je ne pense pas qu'il y ait une bonne raison à cela.)


Addendum (ajouté le 19/05/2012): Sous ARC, vous devez utiliser à la weakplace de assign. Les références faibles sont nilautomatiquement définies lorsque l'objet meurt, ce qui élimine la possibilité que l'objet déléguant finisse par envoyer des messages au délégué mort.

Si vous vous éloignez d'ARC pour une raison quelconque, modifiez au moins les assignpropriétés qui pointent vers des objets unsafe_unretained, ce qui indique clairement qu'il s'agit d'une référence non conservée mais non nulle à un objet.

assign reste approprié pour les valeurs non-objet sous ARC et MRC.

Peter Hosey
la source
13
NSURLConnectionconserve son délégué.
Oui, utilisez weak, mais cela ne répond pas à la question initiale: pourquoi Apple utilise-t-il à la assignplace de weak?
wcochran
@wcochran: La question initiale était «pourquoi les propriétés des délégués sont-elles données assignplutôt que conservées »; weakn'existait pas quand on l'a demandé. Votre question est différente, que vous devriez poser séparément. Je serais ravi d'y répondre.
Peter Hosey
@wcochran et Peter, cette question a-t-elle été posée ailleurs?
Mr Rogers
17

Notez que lorsque vous avez un délégué qui est assigné, il est très important de toujours définir cette valeur de délégué sur nil chaque fois que l'objet va être désalloué - donc un objet doit toujours faire attention à nil out les références de délégué dans dealloc si ce n'est pas le cas. fait ailleurs.

Kendall Helmstetter Gelner
la source
«Notez que lorsque vous avez un délégué qui est assigné, il est très important de toujours définir cette valeur de délégué sur nil chaque fois que l'objet va être désalloué» Pourquoi?
Peter Hosey
2
Parce que toute référence à gauche, sera invalide après la désallocation d'un objet (pointant vers la mémoire qui n'est plus allouée au type d'objet attendu) - et provoquera ainsi un plantage si vous essayez de l'utiliser. Un signe de cela dans le débogueur est lorsque le débogueur affirme qu'une variable a un type qui semble totalement faux par rapport à ce que la variable est réellement déclarée.
Kendall Helmstetter Gelner
1
Cela n'est nécessaire que si l'objet dont vous êtes un délégué est conservé par une autre source, telle qu'un minuteur ou un autre rappel asynchrone. Sinon, il sera libéré après sa libération et n'essaiera pas d'appeler des méthodes de délégué.
Andrew Pouliot
@Andrew: C'est vrai, mais si vous prenez toujours l'habitude de supprimer des délégués, vous n'oublierez pas quand cela compte, ou si vous sur-retenez accidentellement un objet tenu et qu'il reste de toute façon. Si vous supprimez le délégué, le résultat est juste une fuite, au lieu d'une fuite suivie d'un crash.
Kendall Helmstetter Gelner
1

L'une des raisons derrière cela est d'éviter les cycles de rétention. Juste pour éviter le scénario où A et B se référencent tous les deux des objets et aucun d'eux n'est libéré de la mémoire.

L' affectation effective est préférable pour les types primitifs tels que NSInteger et CGFloat, ou les objets que vous ne possédez pas directement, tels que les délégués.

Puneet Sharma
la source
C'est plutôt copié de la citation du PO et de la réponse acceptée respectivement, n'est-ce pas?
dakab