Parler à un zombie
Bonjour,
J'ai beaucoup hésité avant de poster ici, mais je le fais maintenant parce que je ne trouve pas de solution ailleurs, au risque de me faire jeter.
Je suis un MOOC sur le dev iOS et j'ai un exercise simple sur la gestion de la mémoire (sans ARC). C'est un exercice un peu ancien, ce qui explique que je n'aie pas de réponse sur le forum du MOOC. ça fait maintenant 24 h que je cherche, sans résultat.
En résumé, je constate qu'un objet est désalloué dans mon projet, sans que je sache pourquoi/comment !
Comme un petit test vaut mieux qu'un long discours, je poste mon projet ici.
Et je n'explique pas plus parce que j'ai longuement expliqué déjà et je mets cela dans un PDF ici.
Je précise juste que le problème se déclenche par appui sur Rà Z, en général au 2e coup.
Merci de vos lumières.
Réponses
Oui il faut retenir (retain) l'instance de UIColor.
Cette instance ayant été obtenue par une méthode +colorWithRed:green:blue:alpha: est placée dans l'auto-release pool, elle sera automatiquement libérée (release) à chaque boucle d'événement. Le tableau contiendra alors des pointeurs sur des instances toutes libérées mais non nulles, d'où le risque de Bad Access à la boucle d'événement suivante.
Mais c'est vraiment une très très drôle idée de ne pas utiliser ARC.
Si le MOOC parle toujours de MRC, je te conseillerais à trouver un autre stage ; il y a plein d'autre nouveautés en plus
Je présume que c'est le MOOC de FUN ? Dans ce cas c'est normal de ne pas utiliser ARC. L'auteur en a parlé pendant l'introduction. Ce MOOC provient d'un cours universitaire de haut niveau destiné à des bac+5 en informatique. La gestion manuelle de la mémoire est devenue obsolète en programmation iOS avec l'apparition d'ARC, mais reste un concept important à comprendre pour de futurs docteurs en informatique. C'est pourquoi certains cours du MOOC n'utilisent pas ARC, pour entrainer les étudiants aux pièges de la gestion mémoire. Si ton objectif est juste d'apprendre à développer pour iOS, laisse tomber les exercices en Objectif-C sans ARC. C'est un peu comme apprendre le latin dans un cours de français ! Le même MOOC parle aussi de Swift où la gestion mémoire n'existe pas (en fait si, mais Swift utilise ARC sous le capot pour s'occuper de la mémoire sans casser les pieds aux développeurs).
Impressionnant la rapidité de vos réponses.
@jpimbert : merci pour cette piste c'était effectivement ça
Avec le retain c'est beaucoup mieux :
Je ne peux pas le vérifier en profiling parce que je ne maitrise pas complètement l'outil, mais plus de plantage.
@draken : tu as tout compris c'est exactement ça (MOOC de F. Kordon). Même si j'activerai ARC la prochaine fois, je trouve que c'est une très bonne chose de comprendre ce qui se passe en mémoire. Là j'ai compris... donc le but est atteint.
Merci à tous. C'est donc résolu.
Ce n'est pas la réponse qui importe mais le pourquoi cette réponse. La méthode de classe colorWithRed:green:blue:alpha est un constructeur de commodité qui par convention renvoie toujours un objet autorelease. Si on veut le conserver dans tableauCouleurs il est donc impératif de faire un retain qui incrémente le compteur de référence de l'objet.
Mais ce n'est pas suffisant. A chaque passage, on écrase tableauCouleurs[0]. Donc on doit aussi libérer l'ancien objet potentiellement déjà stocké dans tableauCouleurs[0]. Donc un code cohérent serait...
Ou bien encore...
Le meilleur moyen de comprendre les automatismes d'ARC c'est encore de savoir comment ça marche sans ARC. C'est une très bonne chose d'apprendre d'abord comme ça.
Lors d'un entretien d'embauche d'un dev Cocoa, pour moi ne pas être capable de répondre à ce genre de question qui relève du b.a.-ba c'est juste: "Merci au revoir, on vous rappellera".
Merci Mala.
SWIFT POWER !
* cours se planquer *
Franchement, je ne vois pas l'intérêt de connaà®tre le fonctionnement d'un système obsolète. Je partage l'avis de Joanna: tant qu'on comprend la problématique de références fortes et faibles, et les problèmes de cycles de références associés, alors ça me convient.
En quoi est-ce obsolète?!? ARC n'est qu'une surcouche d'automatisation.
Si l'on appele "surcouche" ça implique que l'on ajoute une couche. Mais c'est le compilateur qui introduit les appels à retain, release, etc. ; rien de plus. Le code résultant est exactement pareil qu ce du MRC.
En fait, même pas, le compilateur peut par exemple remplacer un -autorelease par un -release s'il analyse que c'est plus rapide. La gestion automatique est plus performante que la gestion manuelle.
Oui rien de plus donc derrière c'est toujours le même système de gestion mémoire. Donc quelqu'un qui apprend le fonctionnement pré ARC n'a aucune difficulté à appréhender ARC. L'inverse n'est pas vrai et on le voit régulièrement sur le forum. Pire, cette mauvaise connaissance des fondements entraine une mauvaise assimilation d'ARC (ha! ça plante avec un weak. Bon bien je vais mettre un strong alors...).
Et malheureusement pour cette même raison Céroce a tout faux. ARC étant un système de substitution à la compilation, il est loin d'avoir l'intelligence nécessaire à faire mieux que le développeur en terme de performances.
Mais prenons un exemple concrêt. Disons que j'ai une grosse boucle de 1000 itérations. Dans chaque boucle je charge une NSImage.
S'il s'agit d'une routine manipulant des petits bitmaps comme des petits sprites, j'ai tout loisir de ne pas me soucier de l'augmentation de mon pool mémoire. J'aurais alors tendance à l'écrire comme suit...
Les libérations mémoires se feront alors d'un bloc à la fin de ma boucle évènementielle.
S'il s'agit par contre de gros fichiers d'un appareil photo, là une NSImage peut vite monter à plusieurs dizaines de méga octets ce qui pourrait avoir un impact significatif sur mon emprunte mémoire. J'aurais alors plutôt tendance à gérer ma mémoire au plus juste pour chaque boucle...
J'éviterais alors tout risque d'engorgement mémoire pouvant amener à faire swapper mon application.
Le problème c'est qu'ARC étant une optimisation statique au moment de la compilation, il est incapable d'anticiper cette problématique. Son seul et véritable atout est de ne pas être tête en l'air comme un mauvais dev.
Donc je me répète mais oui c'est très bien qu'on apprenne la gestion manuelle avant d'aller plus avant avec ARC et non désolé mais ARC n'est en rien plus performant que la gestion manuelle d'Objective-C.
ARC est tout à fait capable de remplacer un autorelease par un release.
Dans ton exemple, une analyse statique peut tout à fait voir que la NSImage ne sera pas utilisée par la suite, et qu'il n'y a donc pas de raison de la mettre dans l'autorelease pool. Peut-être l'exemple n'est pas bon, mais je ne saisis pas en quoi c'est infaisable.
Si, ARC optimise les retain/release lors de la compilation, comme l'a indiqué Céroce. Il y a eu suffisamment de conférences et doc Apple sur le sujet. Mais c'est du détail d'implémentation.
Par exemple ARC va savoir, par l'analyse de l'AST de ton code, qu'une méthode X n'est appelée que par un code Y, et donc plutôt que de faire un autorelease à la fin de la méthode X et faire un retain quand tu es dans la méthode Y (pour reprendre l'ownership de l'objet dans le scope de Y), il va voir que cet enchaà®nement amène TOUJOURS à un autorelease suivi d'un retain, et il va donc automatiquement enlever les 2 pour optimiser.
Mais ça c'est parce que le compilateur est très poussé et sait faire de l'analyse avancé de ton AST à la compilation.
Mais je pense que je comprends quand même ce que tu veux dire Mala.
Toi tu ne parles pas de ce qui se passe réellement sous le capot (optimisation par le compilateur des balances retain/release) mais de la vue purement conceptuelle. Où à la limite tu t'en fiches que le compilateur optimise ou pas, le concept et l'explication haut niveau reste la même, chaque objet a un ou des "owners", il y a toujours un risque de strong reference cycle et ça c'est pas ARC qui va le résoudre pour toi, et passer à ARC ne veut pas dire oublier les règles de base sur l'ownership, et le risque de strong reference cycle.
En bref, vous avez un peu tous raison, Céroce et Joanna ont raison techniquement, et Mala tu as raison conceptuellement.
Sans ARC, on écrirait : Avec ARC, donc en enlevant du code le autorelease et le retain pour laisser ARC les mettre tout seul, ce que ARC va produire comme code compilé correspondra à l'équivalent de si on avait écrit : Car le compilateur est suffisamment intelligent pour savoir analyser l'AST, voir que myImage est toujours appelé dans un contexte où il est fait un "retain" sur l'objet qu'il retourne, et donc va enlever le autorelease d'un côté et le retain de l'autre pour optimiser. Et s'il voit que dans certains cas tu fais un retain de la valeur de retour et dans d'autres tu le fais pas, il va selon les cas et les analyses de fréquence de passage dans le code soit laisser le autorelease dans la méthode myImage et le retain dans le code appelant, soit enlever le autorelease dans la méthode myImage, et appeler cet autorelease que dans les méthodes appelantes qui ne retenaient pas de référence vers la valeur retournée.
Tout ça a déjà été expliqué dans des confs Apple par des experts LLVM pour ceux qui étaient intéressés par les mécanismes internes de ARC. Je pense que Mike Ash en a aussi parlé sur son blog. Tout ça c'est surtout des détails d'implémentation dont le développeur moyen n'en a cure, car la seule chose que le développeur de base a à respecter c'est les règles d'ownership (réfléchir à quand mettre strong et quand mettre weak, pour pas les mettre au hasard mais comprendre leur différence et pour pas créer de strong reference cycle), car ça c'est toujours important. Comment le compilateur transforme le code à la fin et le fait que le compilateur est toujours plus intelligent que vous pour les analyses poussées de code, ce n'est que du détail qui ne change pas qu'il faut respecter toujours les mêmes principes pour choisir correctement une ownership strong ou weak.
Tu philosophes trop, Ali. La meilleure méthode pour parler avec un zombie c'est le colt 45 ou une grenade !
Le problème principal c'est le silence dont ARC et Objective-C recouvrent nos erreurs... un strong de trop, et on a des zombies qui rôdent autour de l'application, un weak de trop et on se retrouve à envoyer des messages dans le vide. Mais dans les deux cas, pas de plantage " envoyer un message à un objet nil en Swift envoie l'application dans la quatrième dimension, comme au bon vieux temps.
@Draken: if let SWIFT = POWER? {
<se planque aussi>}
- Un strong de trop et on a une fuite mémoire. Certainement pas un zombie.
- Un weak de trop et on envoie des messages dans le vide. Alors qu'avec MRC on a un gros crash violent justement parce que " comme le weak n'existe pas il n'y a que assign ou unsafe_unretained en MRC " l'objet a pu être détruit (ou transformé en zombie si on les a activés) entre temps.
Pour rappel (puisque cela semble nécessaire) un Zombie est un objet qui a été desalloué depuis sa création mais à qui tu essayes d'envoyer un message quand même. Ils n'existent que si tu les actives pour t'aider à déboguer des crash mémoire qui surviennent sinon (quand tu n'actives pas le mode Zombies de ta session de Debug " ou quand tu lancés la vraie appli Release) parce que ton objet aurait disparu : si tu actives le mode Zombie de ton debugger ces objets seront zombifiés au lieu d'être totalement tués juste pour t'aider à déboguer et à retrouver l'objet que tu as releasé une fois de trop où oublié de retain.
Et tout ce mécanisme de zombies n'a rien à voir avec le fait que tu sois en ARC où pas.
Par contre en ARC tu as beaucoup moins de risques d'avoir des objets détruits auxquels tu arriverais à quand même garder une référence pour tenter envoyer des messages si tu utilises ARC et weak au lieu de MRC et unsafe_unretained ; donc tu devrais avoir bien moins souvent le cas de zombies en ARC.
Certes il est quand même possible d'avoir des zombies avec ARC dans des cas rares, en particulier les seuls auquel je pense immédiatement étant quand tu fais une transition vers le monde MRC depuis ARC (genre tu passes par CoreFoundation ou des bouts de code qui ne sont pas managés par ARC) ou quand tu utilises "unsafe_unretained" (et le "unsafe" veut bien dire ce qu'il veut dire !) mais bon tu risques quand même d'en avoir beaucoup + avec MRC et un assign mal placé au lieu d'un strong. Alors qu'en ARC avec un weak grâce aux ZWR elles retombent à nil et donc le risque de problème est beaucoup + les strong référence cycles que les zombies !!
Ah oui, pardon le couple qui tournoie dans le vide c'est une leak, pas un zombie. -1. Ce n'est plus un cerveau que j'ai, c'est de la béchamel.
En trois ans avec ARC, cela m'est arrivé une seule fois d'avoir un zombie, dans le cadre d'une modalSheet qui avait un comportement aberrant. Pour les *leaks*, je fais super gaffe aux retain cycles depuis que j'en ai créé un par mégarde (j'avais eu droit à des explications détaillées sur ce site, par toi il me semble). En fait, j'avais fini par supprimer carrément la référence croisée.
Eviter les références croisées est presque impossible. Le cas d'école étant la délégation, le délégué ayant une référence sur le délégant et vice versa.
Par contre quand on en a, on doit éviter les strong reference cycle.
Autrement dit il faut alors désigner un des 2 objets comme le "owner" = le propriétaire de l'autre objet ; le owner ayant alors une référence strong sur l'objet qu'il possède et l'objet possédé ayant une référence weak / unowned vers son propriétaire.
Tu me connais. Cela fait bien longtemps que je ne crois plus aux belles paroles des évangélistes.
Je veux bien qu'ARC introduise quelques subtilités sous le capot mais de là à dire que du code ARC est plus performant ok, mais encore faut-il être capable de le constater dans la vraie vie: Je veux dire par là des migrations de vrais programmes en production qui montrent que le passage à ARC offre un réel boost appréciable et quantifiable.
Et là malheureusement, ou heureusement car la gestion mémoire originelle d'Obj-C est relativement élégante malgré son âge, en l'état ces optimisations restent de la poudre aux yeux tellement leur impact est négligeable. Et si je me permets de dire ça c'est que j'ai quelques migrations de projets à mon actif.
J'aurais tendance à préférer l'immolation par le feu. Plus bourrin mais radical. Et puis des fois que le zombie soit en plus un hérétique, on fait d'une pierre deux coups...