Framework ARC + "Vrai" code = Crash ?
Dans mon projet de mise en réseau CoreData je viens de tomber sur un mauvais bug... CocoaHTTPServer, le framework HTTP sur lequel je me suis basé, utilise ARC. Pour pas me faire chier avec les flag compilo et pas avoir à utiliser ce que je considère être une grosse daube, j'ai embarqué CocoaHTTPServer dans un framwork. ça fonctionne très bien dans son ensemble sauf que je viens de tomber sur un cas foireux...
Dans la méthode de gestion des réponses aux méthodes HTTP je dois renvoyer un objet type HTTPResponse qui contient header et body à envoyer au client. La méthode que j'implémente ressemble à ceci :
En toute logique, returnHTTPResponse est dans le bassin d'autorelease à la fin de ma méthode.
De l'autre coté de ce code, se trouve le framework CocoaHTTPServer qui fait ceci :
httpResponse étant une ivar.
Il semblerait que le framwork ne fait pas de retain ici. Plus tard dans l'exécution NSZombie me crie dessus par ce qu'un bout du CocoaHTTPServer fait un release sur un objet déjà libéré...
À savoir que l'implémentation mon répondeur est une sous classe du système de base fournis par le framework qui ressemble normalement à ceci :
Est-ce que quelqu'un a une idée sur comment dois-je gérer ma mémoire ? Qu'est-ce que je suis censé renvoyer à ARC pour qu'il soit content ?
Dans la méthode de gestion des réponses aux méthodes HTTP je dois renvoyer un objet type HTTPResponse qui contient header et body à envoyer au client. La méthode que j'implémente ressemble à ceci :
<br />
- (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path<br />
{<br />
__block NSObject<HTTPResponse> * returnHTTPResponse = nil;<br />
<br />
<snip><br />
<br />
if ([method isEqualToString:@"GET"]) {<br />
returnHTTPResponse = [self methodGETWithPath:path andPathCompontents:pathComponents];<br />
} else if ([method isEqualToString:@"POST"]) {<br />
ORrunOnMainQueueWithoutDeadlocking(^{<br />
returnHTTPResponse = [self methodPOSTWithPath:path andPathCompontents:pathComponents];<br />
});<br />
} else if ([method isEqualToString:@"PUT"]) {<br />
ORrunOnMainQueueWithoutDeadlocking(^{<br />
returnHTTPResponse = [self methodPUTWithPath:path andPathCompontents:pathComponents];<br />
});<br />
} else if ([method isEqualToString:@"DELETE"]) {<br />
ORrunOnMainQueueWithoutDeadlocking(^{<br />
returnHTTPResponse = [self methodDELETEWithPath:path andPathCompontents:pathComponents];<br />
});<br />
} else {<br />
// Unknow method<br />
}<br />
<br />
} <br />
<br />
<br />
<snip><br />
<br />
if (returnHTTPResponse) {<br />
return returnHTTPResponse;<br />
}<br />
<br />
return [super httpResponseForMethod:method URI:path];<br />
}<br />
En toute logique, returnHTTPResponse est dans le bassin d'autorelease à la fin de ma méthode.
De l'autre coté de ce code, se trouve le framework CocoaHTTPServer qui fait ceci :
<br />
// Respond properly to HTTP 'GET' and 'HEAD' commands<br />
httpResponse = [self httpResponseForMethod:method URI:uri];<br />
httpResponse étant une ivar.
Il semblerait que le framwork ne fait pas de retain ici. Plus tard dans l'exécution NSZombie me crie dessus par ce qu'un bout du CocoaHTTPServer fait un release sur un objet déjà libéré...
À savoir que l'implémentation mon répondeur est une sous classe du système de base fournis par le framework qui ressemble normalement à ceci :
<br />
- (NSObject<HTTPResponse> *)httpResponseForMethod:(NSString *)method URI:(NSString *)path<br />
{<br />
HTTPLogTrace();<br />
<br />
// Override me to provide custom responses.<br />
<br />
NSString *filePath = [self filePathForURI:path allowDirectory:NO];<br />
<br />
BOOL isDir = NO;<br />
<br />
if (filePath && [[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDir] && !isDir)<br />
{<br />
return [[HTTPFileResponse alloc] initWithFilePath:filePath forConnection:self];<br />
<br />
// Use me instead for asynchronous file IO.<br />
// Generally better for larger files.<br />
<br />
// return [[[HTTPAsyncFileResponse alloc] initWithFilePath:filePath forConnection:self] autorelease];<br />
}<br />
<br />
return nil;<br />
}<br />
Est-ce que quelqu'un a une idée sur comment dois-je gérer ma mémoire ? Qu'est-ce que je suis censé renvoyer à ARC pour qu'il soit content ?
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Par safe-return, j'entend:
[/size][/font][/color]
Aussi, es-tu sur qu'il n'y aurait pas une de ces méthodes qui ne retournerait pas ton objet avec un release en trop?
Concernant mes méthodes, j'en suis certain d'autant que je ne travaillais que sur une seule.
Bref, actuellement ça fonctionne mais je suis pas du tout confiant sur la partie mémoire...
Le compilateur est capable de se rendre compte tout seul que ces casts manquent et balance une jolie erreur. De plus, ces mots clés ne sont fonctionnels que quand on compile avec ARC activé... Si j'ai bien compris, yoann utilise une librairie tierce utilisant ARC, mais son code ne l'utilise pas. Il n'a donc pas de raison d'utiliser ces mots clés dans son code.
Non pas uniquement . ARC est plus général, c'est une fonctionnalité d'un compilateur objective-C. Cocoa et CF sont des libraries (resp. Objective C et C).
Bon j'admets que je ne connais pas beaucoup d'autres librairie C qui renvoie des types opaques qui sont "reference counted".
ça, c'est juste faux.
Un exemple :
Vu ici : http://www.mikeash.c...-counting.html
(D'autre part tu me donneras d'autres exemples de pointeur non typé que void *.)
J'ai jamais dit qu'il devait les utiliser dans son code. CocoaHTTPServer est open source et ce n'est pas initialement un framework il me semble. C'est bien dans le framework qu'il faudrait ajouter les mots clés.
Du point de vue du framework le code de Yoann est opaque et renvoie des objets qui sont reference counted donc il ne me parait pas complétement idiot d'utiliser les bridged cast. La situation est similaire à un code ARC utilisant CoreFoundation.
On est en face d'un problème de transfert de propriété entre ARC et non-ARC et les bridged casts servent justement à résoudre ces transferts. C'est pour cette raison que j'ai dit de regarder dans cette direction. Je sais bien que je ne m'adresse pas à un débutant qui va appliquer sans comprendre.
Cela dit, tes remarques m'ont obligés à essayer de déchiffrer la doc de ARC, et on y parle de casts entre "retainable object pointer type" et "non-retainable pointer type". Donc le cas de yoann qui se passe entre deux retainable object pointer type ne serait effectivement pas concerné.
Si le problème était reproductible, je pense que cela vaudrait quand même le coup d'essayer. Ne serait-ce que pour mieux comprendre comment fonctionne ARC.
Certes, mais en pratique, sur les plateformes Apple, ce dont je parle depuis le début (je ne l'ai effectivement pas mentionné, mais je ne suis pas là pour faire de la théorie sur la compilation de code Objective-C en général, et de toute façon, je cherche encore une autre plateforme qui utilise Objective-C, hormis l'anecdotique GNUStep), on n'utilise jamais __bridge dans ce cas, puisque ton exemple est strictement équivalent à :
ou encore à
et, toujours sur plateformes Apple, __bridge_transfer et __bridge_retained n'ont d'utilité que pour le tollfree bridging entre Cocoa et CF.
Donc, pour généraliser (mais du coup je suis sûr que c'est moins clair pour le développeur Cocoa moyen):
J'aime vraiment, mais alors vraiment pas ARC...
Je crois pourtant qu'il va falloir que tu y passes...
Parce que si mon sentiment était jusqu'à présent qu'Apple laisserait le choix aux développeurs, la terminologie qu'ils utilisent actuellement ("transition to ARC") me laisse de plus en plus penser qu'à terme il n'y aura plus de choix possible. Mais ce n'est pas pour tout de suite, car Mountain Lion (et Xcode 4.4 DP4) supporte toujours le code non ARC.
Et clairement, j'ai beau retourner le problème dans tous les sens, je ne comprends toujours pas les réticences à utiliser ARC... ARC n'est pas un garbage collector, ARC n'a pas les impacts d'un garbage collector en terme de performances. ARC n'a que des avantages vis à vis de la gestion de mémoire manuelle ou d'une gestion mémoire par un GC.
J'aurais préféré qu'Xcode propose de voir en grisé le code généré par ARC histoire de savoir ce qu'il se passe réellement et de ne pas attendre de voir des bug d'exécution pour voir qu'on a oublier d'ajouter certain mot clef ésotérique d'ARC.
On doit jouer avec un truc qui nous gère la mémoire tout seul mais qui n'affiche pas son travail. J'aime pas du tout...
Tu ne peux pas oublier, puisque tout oubli se solde par une erreur de compilation.
Et c'est bien un autre avantage de ARC par rapport à la gestion manuelle: Là où la gestion manuelle nécessite une réflexion systématique, et où tout oubli se traduit par un plantage ou un leak à l'exécution, ARC permet de porter son attention sur le code et prévient par une erreur de compilation là où il n'est pas capable de se débrouiller seul: Si tu fais un cast en oubliant le mot clé approprié, le compilateur ne laissera pas passer...
Ne code jamais en ruby, python ou perl alors. Parce que grosso modo ces langages utilisent un modèle de gestion mémoire proche de ARC. Et ils ont tous prouvé leur efficacité.
Savoir ce qu'il se passe en terme de comportement mémoire c'est juste primordial pour optimiser des opérations...
Je ne dis pas que ARC n'est pas bien, je crois même qu'il est certainement très efficace.
Mais :
- Le système de retain/release est simple à comprendre je trouve aussi comme yoann, chacun est responsable des objets qu'il possède, ça m'a toujours semblé logique
- Je n'aime pas ne pas savoir ce qui est fait sous le capot en terme de gestion mémoire, surtout sur un programme à vocation de tourner sur de l'embarqué. C'est plus fort que moi, j'aime pas quand c'est obscur et nébuleux de savoir où sont retenus mes objets, etc*
- Mais aussi ça n'aide pas les nouveaux venus à comprendre la logique de fonctionnement. Or pour moi certes c'est bien de faciliter le travail du développeur, mais j'ai toujours peur (craintes un peu confirmées d'ailleurs ces derniers temps) que ça incite encore moins les noobs à comprendre ce qu'ils font avec leur code, et donc que ça augmente d'une certaine manière le nombre d'applis "code torchon"... et les boulets qui vont croire que coder c'est facile et même pas faire d'architecture mais direct copier/coller des bouts de code entre eux...
Bref après c'est juste une question de préférence personnelle, ARC est certainement très bien et très efficace, y'a pas de soucis. C'est juste que ça me donne l'impression de perdre le contrôle.
Les systèmes de diagnostic électroniques sur les voitures modernes où le mécano branche son appareil et regarde sur le moniteur le résultat des sondes, c'est bien, c'est plus simple pour lui, il n'a plus à se soucier de mettre les mains dans le moteur. Mais le jour où ça déconne, ou les sondes elles-mêmes ne marchent plus... bah le mécano a perdu l'habitude de regarder dans le moteur, voire les nouveaux ne sauront même plus comment faire, et au final s'ils finissent par ne plus que brancher un câble sur leur moniteur sans savoir mettre les mains dans le moteur quand y'a vraiment besoin, je trouve ça plutôt gênant...
En ce qui concerne les optimisations, ARC permet justement des optimisations impossibles à faire avec une gestion de mémoire manuelle (notamment quand le code est 100% ARC, le runtime, par analyse de la pile d'exécution, est capable de ne pas retain+autorelease les objets retournés par les méthodes).
L'excellent article de Mike Ash (comme tous ses articles d'ailleurs, je suis un grand fan), cité par FKDEV, explique tout cela très clairement.
Edit: Sinon, vous pouvez toujours aller voir le source du runtime sur opensource.apple.com, c'est vraiment très intéressant et commenté:
Sauf que du coup on passe à côté de risques, par exemple le cas des zeroing weak references avec du multithreading dont Mike Ash parle justement.
C'est un des cas où justement il faut faire attention et savoir ce qu'on fait et ce que ARC fait sous le capot pour penser à transformer la weak reference en strong reference localement pour rester thread-safe... (et où le compilateur ne nous dira rien si on ne le fait pas... et en plus les erreurs comme ça risquant des bugs en cas de race-conditions sont particulièrement difficiles à tracker, justement à cause du race-condition qui fait que ça n'arrive pas à tous les coups selon le scheduling des divers threads)
Bref, certes c'est un cas peu commun (mais justement raison de plus pour qu'on risque de passer à côté ne le rencontrant pas souvent), mais ce que je veux dire par là c'est que ce n'est pas parce qu'on utilise ARC qu'il faut totalement arrêter de réfléchir à comment est géré la mémoire... or pourtant vu ce qu'ARC fait tout seul sans nous dire, ça aurait plutôt tendance à nous faire tout oublier...
J'aime bien la sémantique explicite de __strong et __weak.
En plus je ne savais même pas que __weak remettait à nil les pointeurs automatiquement.
Maintenant, dans des cas particuliers, il y a peut-être moyen de forcer une gestion manuelle en utilisant des types opaques persos et des bridge casts, non ?
C'est juste une intuition, hein, je n'ai pas le temps ni le courage de me pencher là -dessus. Mais si c'est possible quelqu'un comme mikeash nous sortira un article.
Sur ce point je suis entièrement d'accord. Mais dans la majorité des cas, la gestion mémoire manuelle, c'est du systématique. Le systématique, autant le faire faire à une machine, tu risques moins l'erreur d'inattention parce que c'est tellement systématique que ça en devient la routine.
Mais clairement, pour les cas particuliers, comme les références weak, qu'il est fortement recommandé de transformer en références strong pour ne pas voir son objet disparaitre en plus milieu d'un bout de code qui l'utilise, ou les retain cycles, le compilateur ne va pas remplacer le cerveau (bien qu'il pourrait très bien générer un warning quand on utilise une variable weak pour autre chose qu'une assignation à une variable strong).
Attention, uniquement sous iOS 5 et MacOS X 10.7.
Sinon, pour les autres plateformes (iOS 4.2 et MacOS X 10.6), il est possible d'utiliser PLWeakCompatibility.