Depuis 2005, je me suis beaucoup intéressé publiquement à l'évolution à long terme du langage de programmation d'Apple et des framework d'applications, à Objective C, et Cocoa, en allant jusqu'à imaginer une crise technologique possible dans le futur.
Quand le futur est arrivé, j'ai reconsidéré le problème du langage d'Apple et du futur des APIs à la lumière de la pénétration spectaculaire d'Apple dans le marché des mobiles et de la croissance sans précédent qu'elle avait permise. Vous pouvez vous-même lire mes conclusions, mais l'essentiel est que je suis toujours inquiet à ce sujet, et je pense que Apple devrait l'être aussi. Le succès cache les difficultés et Apple a été si profitable ces dernières années.
Apple a fait un travail considérable pour moderniser sa plateforme de développement, y compris le remplacement complet de son compilateur, la remise à plat de son EDI (Environnement de développement Intégré), et en ajoutant des caractéristiques et une nouvelle syntaxe au langage Objective C lui-même.
Toutes ces choses là sont bonnes, mais aucune ne satisfait mes préoccupations spécifiques sur la gestion de mémoire. Apple s'est finalement résolue à rajouter le ramasse miettes à Objective C, mais ma crainte qu'Apple ne se soit pas réellement engagée pour le ramasse miettes dans objective C s'est révélée bien fondée. Aujourd'hui, des années après l'introduction de cette caractéristique, très peu d'applications d'Apple utilisent le ramasse miettes.
Il y a une bonne raison à cela. Le ramasse miettes à l'exécution s'applique mal, tout simplement, à Objective C. Pour sa simplicité syntaxique, et sa longue histoire couronnée de succès, le langage de programmation C est en réalité une bête étonnamment complexe, en particulier en ce qui concerne la gestion de mémoire. Sous C, tout ensemble de bits de taille pointer correctement aligné en mémoire peut être utilisé comme une adresse ; le langage autorise explicitement le passage de void * au type pointeur, et vice-versa. Obective C, en tant que sur-ensemble du C hérite de ces charmantes propriétés. En échange de ce sacrifice, le code d'objective C peut être compilé parallèlement à du vrai code C, et peut se lier facilement aux bibliothèques C.
Il semble avisé pour le ramasse miettes d'avoir une approche prudente à toute mémoire allouée en dehors de la communauté orientée objet limitée d'Objective C. Malheureusement, la mémoire allouée "à l'ancienne mode" dans le code C fait son chemin normalement dans le monde d'Objective C, et vice-versa. En théorie, tout ce genre de code pourrait être annoté de façon à ce que ça fonctionne correctement avec le ramasse miettes. Dans la pratique, Mac OS X contient beaucoup trop de code -la plus grande partie non écrite par Apple- pour un examen correct de chaque ligne, afin de s'assurer que le ramasse miettes a assez d'information à l'exécution pour prendre les bonnes décisions dans tous les cas.
<:a> Et de fait, en dépit des assurances d'Apple sur la faisabilité, il y a eu et il continue à y avoir des cas où même le code à l'intérieur des frameworks propres à Apple peut abuser le ramasse miettes d'Objective C. Ce genre de bogue est particulièrement insidieux, parce qu'elle peut se manifester seulement quand le ramasseur tourne depuis un certain temps. L'état de compatibilité du ramasse miettes pour les bibliothèques de tierces partie est encore plus désolant.
Pour faire court ; le ramasse miettes pour Objective C est hors jeu. (Il est encore supporté par Lion, mais je ne compterais pas sur Apple pour y investir un effort considérable susceptible de le remettre en selle). Et ne soyez pas surpris s'il prend le chemin de Rosetta dans quelques années. Pour le remplacer, Apple a créé quelque chose appelé Comptage de Référence Automatique ou ARC (Automated Reference Counting) en bref. Mais pour comprendre ARC, il vous faut d'abord comprendre comment la gestion de mémoire a traditionnellement fonctionné sous Cocoa.
Cocoa utilise une technique de gestion de mémoire appelée le comptage de références. Chaque objet a un compte de références qui lui est associé. Quand un élément dans une application prend possession de l'objet, il incrémente le compte de références de l'objet en lui envoyant un message Cela permet à un objet donné d'être utilisé par différents éléments de l'application, chacun d'eux étant responsable d'encadrer l'usage de l'objet par les messages Pour finir, il y a le message C'est simple, non ? Assurez-vous seulement que vos messages Mais l'éducation et les outils ont une limite. Les experts Cocoa peuvent ne pas voir la gestion de mémoire avec Pour comprendre comment ARC fonctionne, commencez par vous représenter un fichier de code source traditionnel Objective C écrit par un programmeur Cocoa expert. Les messages Maintenant, imaginez que vous éditez ce fichier de code source, en supprimant toute instance des messages Les réglages d'ARC dans XCode (le renforcement est ajouté).
Avant d'expliquer comment ARC procède, il est important de comprendre ce que ne fait pas ARC. D'abord, ARC n'impose pas un nouveau modèle de mémoire à l'exécution. Le code compilé avec ARC utilise le même modèle de mémoire que celui du C normal, ou du code Objective C sans ARC, et peut être lié à toutes les mêmes bibliothèques. Ensuite, ARC fournit une gestion de mémoire automatique seulement pour les objets d'Objective C (notez cependant que les blocs se trouvent être aussi des objets Objective C sous le capot). La mémoire allouée de toute autre façon n'est pas affectée, et doit encore être gérée manuellement. (La même chose vaut pour les ressources comme les poignées sur les fichiers et les sockets). Enfin, ARC n'est pas du ramasse miettes. Il n'y a aucun processus qui parcourt l'image en mémoire d'une application en fonctionnement à la recherche de mémoire à désallouer. Tout de que fait ARC intervient au moment de la compilation.
Ce que fait ARC à la compilation n'est pas magique. Il n'y a pas d'intelligence artificielle élaborée là dedans. ARC n'utilise même pas l'analyseur statique sophistiqué de LLVM pour se représenter l'endroit où mettre les
Ce qui permet à ARC de fonctionner, est la même chose qui permet aux gens de devenir (occasionnellement) des programmeurs experts en Cocoa : les conventions. Cocoa a des règles sur le transfert de propriété qui interviennent lors d'opérations courantes comme la définition ou la récupération d'un attribut d'objet, l'initialisation d'un objet, ou l'obtention d'une copie mutable. En plus, les méthodes qui implémentent ces opérations suivent un ensemble de conventions de d'écriture. ARC connaît toutes ces règles, et les utilise pour décider quand il doit retenir ( En fait, ARC suit ces règles d'un manière plus formaliste que ne le ferait un humain, en encadrant toute opération qui pourrait être influencée par la possession d'un objet avec des messages Les conventions sont faites pour être contournées, bien sûr. Mais ce que ARC n'a pas en sophistication sémantique, il le rattrape en prédictibilité, et en vitesse, en vitesse, en vitesse. Dans les cas où l'homme est plus savant, on peut dire à ARC exactement quoi faire grâce à un ensemble étendu de nouveaux attributs et de macros qui permettent au développeur d'annoter des variables, des structures de données, des méthodes et des paramètres, avec des instructions explicites pour ARC. Mais l'idée derrière ARC est que les exceptions doivent être rares.
Pour s'assurer que ARC peut faire d'une façon correcte ce qu'il est sensé faire, quelques restrictions supplémentaires de langage ont été rajoutées. La plupart sont ésotériques, et interviennent aux limitres entre le code C normal et le code Objective C (par exemple, les struct et les unions C n'ont pas le droit de contenir des références à des objets Objective C). La compatibilité avec le code C existant est une des grandes forces d'Objective C. Mais comme ARC est une caractéristique qui fonctionne par unité de compilation, et que du code ARC et non-ARC peut être mélangé librement, ces nouvelles restrictions de langage rendent ARC plus fiable, sans compromettre l'inter-opérabilité.
La gestion de mémoire sous Cocoa
retain. Quand il a terminé avec l'objet, il décrémente le compte de références en envoyant à l'objet le message release. Quand le compte de références d'un objet est à zéro, il est désalloué.
retain et release. Si retain est envoyé à l'objet un plus grand nombre de fois que release, alors, le compte de références n'atteindra jamais zéro, et sa mémoire ne sera jamais libérée. Cela s'appelle une fuite de mémoire. Si release est envoyé un plus grand nombre de fois que retain, alors, un message release envoyé après que le compte de références a atteint zéro se retrouvera utilsant une région de la mémoire précédemment occupée par l'objet, qui peut maintenant contenir tout autre chose. Habituellement, il s'ensuit un crash.
autorelease qui signifie "relâcher, mais plus tard". Quand un objet reçoit le message autorelease il est rajouté au "pool autorelease" courant. Quand ce pool est appelé, tous les objets qu'il contient reçoivent un message release pour chaque fois où ils ont été ajoutés au pool. (un objet peut être ajouté au même pool autorelease de multiples fois). Les applications Cocoa ont un pool autorelease qui est visité à la fin de chaque boucle d'évènements, mais de nouveaux pools peuvent être créée localement par le programmeur.
retain et release/autorelease sont équilibrés, et vous êtes bien. Mais, aussi simple que ce soit conceptuellement, il est en réalité étonnamment facile d'être pris en défaut. Les programmeurs Cocoa expérimentés vous diront que cette gestion de mémoire avec retain/release finit par devenir une seconde nature -et c'est le cas-, mais les programmeurs ne sont que des hommes. La traque précise du cycle de vie de tous les objets dans une grosse application commence à excéder les limites des capacités mentales de l'homme. Pour l'aider, Apple fournit des outils de développement sophistiqués pour traquer les allocations de mémoire et faire la chasse aux fuites.
retain/release comme une problème, mais Apple est tournée vers le futur, vers de nouveaux développeurs. Les autres plateformes mobiles ou de bureau n'exigent pas cette forme de gestion de mémoire manuelle dans leurs frameworks d'application de haut niveau. En se basant sur les efforts passés d'Apple pour le ramasse miettes, il semble évident qu'Apple croit que ce serait mieux pour la plateforme si les développeurs n'avaient pas à gérer la mémoire manuellement. Maintenant, en fin de compte, Apple croit avoir trouvé une solution sur laquelle elle peut vraiment se reposer.
L'entrée de ARC
retain, release et autorelease sont tous envoyés au bon endroit, et sont parfaitement équilibrés.
retain, release et autorelease, et en changeant un seul réglage de construction dans XCode pour dire au compilateur d'incorporer tous les appels appropriés de gestion de mémoire dans le programme quand le code source est compilé. Voilà ARC. Comme son nom l'indique, c'est seulement un comptage de références traditionnel de Cocoa fait automatiquement.
retains, et les releases. L'analyseur syntaxique prend beaucoup de temps, trop pour être un élément obligatoire d'un processus de construction ; il peut aussi produire des assertions fausses. Il est bien comme outil pour détecter de possibles bogues, mais une gestion de mémoire fiable exige la certitude.
retain), et quand il doit relâcher (release)..
retain et release. Cela peut provoquer une énorme quantité d'opérations de gestion de mémoire. Heureusement, Apple dispose d'un excellent compilateur avec optimisation, appelé Clang (rebaptisé depuis en Apple LLVM Compiler 3.0 par les génies de marketing d'Apple). Clang parcourt cette mer de code généré mécaniquement , détecte et élimine les redondances jusqu'à ce que, ce qui reste ressemble beaucoup à ce qu'un humain aurait écrit.