38 votes

L'appel à -retainCount considéré comme nuisible

Ou, pourquoi je n'ai pas utilisé retainCount Mes vacances d'été

Ce billet a pour but de solliciter des écrits détaillés sur le pourquoi et le comment de cette méthode infâme, retainCount afin de consolider les informations pertinentes qui circulent sur SO.*.

  1. L'essentiel : Quelles sont les raisons officielles de ne pas utiliser retainCount ? I du tout quand cela pourrait être utile ? Qu'est-ce qui devrait être fait à la place ? N'hésitez pas à faire des commentaires.

  2. Historique/explicatif : Pourquoi Apple fournit-il cette méthode dans le NSObject protocole si elle n'est pas destinée à être utilisée ? Est-ce que le code d'Apple repose sur retainCount dans un but précis ? Si oui, pourquoi n'est-il pas caché quelque part ?

  3. Pour une meilleure compréhension : Quelles sont les raisons pour lesquelles un objet peut avoir un nombre de retenue différent de celui qui serait supposé à partir du code utilisateur ? Pouvez-vous donner des exemples*** de procédures standard que le code cadre pourrait utiliser et qui provoqueraient une telle différence ? Existe-t-il des cas connus où le compte de conservation est toujours différent de ce qu'un nouvel utilisateur pourrait attendre ?

  4. Tout autre sujet que vous jugez utile de mentionner. retainCount ?


* Les codeurs qui découvrent Objective-C et Cocoa ont souvent du mal à comprendre le système de comptage des références, ou du moins à le comprendre. Les explications des tutoriels peuvent mentionner des comptes de retenue, qui (d'après ces explications) augmentent d'une unité lorsque l'on appelle <code>retain</code> , <code>alloc</code> , <code>copy</code> etc., et diminuez d'une unité lorsque vous appelez <code>release</code> (et à un moment donné dans le futur lorsque vous appelez <code>autorelease</code> ).

Un hacker Cocoa en herbe, Kris, pourrait donc facilement avoir l'idée que vérifier le retain count d'un objet serait utile pour résoudre certains problèmes de mémoire, et, voilà, il y a une méthode disponible sur chaque objet appelée <code>retainCount</code> ! Kris appelle <code>retainCount</code> sur quelques objets, et celui-ci est trop haut, et celui-là est trop bas, et que diable se passe-t-il ? ! Alors Kris fait un post sur SO, "Qu'est-ce qui ne va pas avec ma gestion de la mémoire ?" et ensuite une nuée de lettres <gras>, <gros> descendent en disant "Ne fais pas ça ! Vous ne pouvez pas vous fier aux résultats", ce qui est bien, mais notre codeur intrépide pourrait vouloir une explication plus profonde.

J'espère que cela se transformera en une FAQ, une page de bons essais/conférences informatifs de nos experts qui sont enclins à en écrire une, vers laquelle les nouveaux adeptes de Cocoa pourront être dirigés lorsqu'ils s'interrogent sur <code>retainCount</code> .

** Je ne veux pas que cela soit trop vaste, mais des conseils spécifiques tirés de l'expérience ou de la documentation sur la vérification/débogage du maintien et de l'appariement des versions peuvent être appropriés ici.

***Dans un code fictif ; il est évident que le grand public n'a pas accès au code réel d'Apple.

29voto

justin Points 72871

L'essentiel : Quelles sont les raisons officielles de ne pas utiliser retainCount ?

La gestion de l'autorelease est la plus évidente : vous n'avez aucun moyen de savoir avec certitude combien de références représentées par l'objet retainCount sont dans un pool d'autorelease local ou externe (sur un thread secondaire, ou dans le pool local d'un autre thread).

De plus, certaines personnes ont des difficultés avec les fuites, et à un niveau plus élevé, le comptage des références et le fonctionnement des pools d'autorelease à des niveaux fondamentaux. Ils écriront un programme sans se soucier (ou presque) du comptage de référence correct, ou sans apprendre à compter correctement. Cela rend leur programme très difficile à déboguer, à tester et à améliorer - c'est aussi une rectification qui prend beaucoup de temps.

La raison pour laquelle nous décourageons son utilisation (au niveau du client) est double :

1) La valeur peut varier pour de nombreuses raisons. Le filage à lui seul est une raison suffisante pour ne jamais lui faire confiance.

2) Vous devez encore mettre en œuvre un comptage correct des références. retainCount ne vous sauvera jamais d'un comptage de référence déséquilibré.

Y a-t-il jamais une seule situation où cela pourrait être utile ?

Vous pourrait En fait, vous ne pourriez pas l'utiliser de manière significative si vous écriviez vos propres allocateurs ou votre propre système de comptage de références, ou si votre objet vivait sur un seul thread et que vous aviez accès à n'importe quel système de comptage de références. et tous les pools d'autorelease dans lesquels il pourrait exister. Cela implique également que vous ne le partagerez pas avec des API externes. La façon la plus simple de simuler ceci est de créer un programme avec un thread, zéro autorelease pool, et de faire votre comptage de référence de la façon "normale". Il est peu probable que vous ayez un jour besoin de résoudre ce problème ou d'écrire ce programme pour des raisons autres que "académiques".

Comme aide au débogage : vous pourrait utilisez-le pour vérifier que le nombre de retenues n'est pas anormalement élevé. Si vous adoptez cette approche, soyez attentif aux variations d'implémentation (certaines sont citées dans ce billet), et ne vous y fiez pas. Ne commettez même pas les tests dans votre référentiel SCM.

Il peut s'agir d'un diagnostic utile dans des circonstances extrêmement rares. Il peut être utilisé pour détecter :

  • Sur-entretien : Une allocation avec un déséquilibre positif dans le nombre de retenues n'apparaît pas comme une fuite si l'allocation est accessible par votre programme.

  • Un objet qui est référencé par de nombreux autres objets : Une illustration de ce problème est une ressource ou une collection partagée (mutable) qui fonctionne dans un contexte multithread - l'accès ou les changements fréquents à cette ressource/collection peuvent introduire un goulot d'étranglement significatif dans l'exécution de votre programme.

  • Niveaux de libération automatique : La location automatique, les pools de location automatique et les cycles de maintien/libération automatique ont tous un coût. Si vous devez minimiser ou réduire l'utilisation et/ou la croissance de la mémoire, vous pourriez utiliser cette approche pour détecter les cas excessifs.

D'après le commentaire de Bavarious (ci-dessous) : une valeur élevée peut également indiquer une allocation invalidée (instance désallouée). Il s'agit d'un détail d'implémentation, et encore une fois, non utilisable dans le code de production. La messagerie de cette allocation entraînerait une erreur lorsque les zombies sont activés.

Que faut-il faire à la place ?

Si vous n'êtes pas responsable du retour de la mémoire à self (c'est-à-dire que vous n'avez pas écrit d'allocateur), laissez-le tranquille - il est inutile.

Vous devez apprendre à compter correctement les références.

Pour mieux comprendre l'utilisation de release et autorelease, installez quelques points d'arrêt et comprenez comment ils sont utilisés, dans quels cas, etc. Vous devrez toujours apprendre à utiliser correctement le comptage de références, mais cela peut vous aider à comprendre pourquoi il est inutile.

Encore plus simple : utiliser Instruments pour suivre les allocations et les comptages de réf, puis analyser les comptages de réf et les callstacks de plusieurs objets dans un programme actif.

Historique/explicatif : Pourquoi Apple fournit-elle cette méthode dans le protocole NSObject si elle n'est pas destinée à être utilisée ? Le code d'Apple s'appuie-t-il sur retainCount dans un but précis ? Si oui, pourquoi n'est-il pas caché quelque part ?

Nous pouvons supposer qu'il est public pour deux raisons principales :

1) Comptage des références correctes dans les environnements gérés. C'est bien pour les allocateurs d'utiliser retainCount -- vraiment. C'est un concept très simple. Quand -[NSObject release] est appelé, le compteur ref (sauf s'il est surchargé) peut être appelé, et l'objet peut être désalloué si retainCount est 0 (après avoir appelé dealloc). Tout ceci est très bien au niveau de l'allocateur. Les allocateurs et les zones sont (largement) abstraits donc... cela rend le résultat sans signification pour les clients ordinaires. Voir le commentaire avec bbum (ci-dessous) pour plus de détails sur le pourquoi de cette situation. retainCount ne peut pas être égal à 0 au niveau du client, de la désallocation des objets, des séquences de désallocation, etc.

2) Pour la rendre disponible aux sous-classeurs qui veulent un comportement personnalisé, et parce que les autres méthodes de comptage de références sont publiques. Cela peut être pratique dans certains cas, mais c'est typiquement utilisé pour de mauvaises raisons (par exemple, les singletons immortels). Si vous avez besoin de votre propre schéma de comptage de références, alors cette famille peut valoir la peine d'être surchargée.

Pour une meilleure compréhension : Quelles sont les raisons pour lesquelles un objet peut avoir un nombre de retenue différent de celui qui serait supposé par le code utilisateur ? Pouvez-vous donner des exemples*** de procédures standard que le code du framework pourrait utiliser et qui provoqueraient une telle différence ? Existe-t-il des cas connus où le nombre d'objets conservés est toujours différent de ce qu'un nouvel utilisateur pourrait attendre ?

Encore une fois, une référence personnalisée comptant les schémas et les objets immortels. NSCFString appartiennent à cette dernière catégorie :

NSLog(@"%qu", [@"MyString" retainCount]); 
// Logs: 1152921504606846975

Autre chose qui mérite d'être mentionné à propos de retainCount ?

C'est inutile comme aide au débogage. Apprenez à utiliser les analyses de fuites et de zombies, et utilisez-les souvent -- même après vous maîtrisez le comptage des références.


Mise à jour : bbum a récemment publié un article intitulé retainCount est inutile . L'article contient une discussion approfondie sur les raisons pour lesquelles -retainCount n'est pas utile dans la grande majorité des cas.

1voto

Joshua Weinberg Points 22701

En règle générale, si vous utilisez cette méthode, vous avez intérêt à être sûr de savoir ce que vous faites. Si vous l'utilisez pour déboguer une fuite de mémoire, vous vous trompez, si vous l'utilisez pour voir ce qui se passe avec un objet, vous vous trompez.

Il y a un cas où je l'ai utilisé, et où je l'ai trouvé utile. C'est en faisant un cache d'objet partagé où je voulais vider l'objet quand plus rien n'avait de référence à lui. Dans cette situation, j'ai attendu jusqu'à ce que le retainCount soit égal à 1, et alors je peux le libérer en sachant que rien d'autre ne le retient, cela ne fonctionnera évidemment pas correctement dans les environnements de garbage collected et il y a de meilleures façons de le faire. Mais c'est toujours le seul cas d'utilisation "valide" que j'ai vu pour cela, et ce n'est pas quelque chose que beaucoup de gens vont faire.

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