Plantage lors du test d'une appli (ok en débogage)
Bonjour,
Je teste depuis peu mon appli sur un iphone 5S.
J'ai une étape où je récupère des données depuis un webservice et stocke cela avec core data.
En débogage, quand l'iphone est connecté à mon mac, tout se passe bien. Par contre, quand je fais la même manip sur l'iphone en lançant l'appli sans être connecté au mac, l'appli plante systématiquement à cette même étape...
Je suis un peu nouveau dans le développement en objective C et je présume qu'il y a probablement une raison simple à ce plantage ?
Merci pour votre aide
Edit : petite précision, quand l'iphone est relié au mac et que je lance l'appli sans être en débogage sous x-code, je n'ai pas de plantage...
Connectez-vous ou Inscrivez-vous pour répondre.
Réponses
Il faudrait que tu récupères le log crash et le symbolize.
Quand je synchronise mon iphone avec le mac, je n'ai pas de dossier CrashReporter dans les logs...
Sinon via XCode, quand je vais dans la fenêtre Organizer, j'ai un "No device Logs".
Oups, j'étais pas dans le bon dossier logs...
Voici le log du plantage, si cela évoque quelque chose à quelqu'un, merci !
J'avais une petite idée sur la question et après avoir tapé ton erreur sur Google, il semblerai que ce soit un problème avec CoreData. Après je ne connais pas trop ton code donc je ne peux pas vraiment savoir. Mais tape simplement "KERN_INVALID_ADDRESS at 0x0000000000000010 core data" sur Google tu devrais peut-être trouver la réponse à ta question.
Ok, merci pour ton aide, je vais creuser dans ce sens...
En fait le plantage se produit, quand je stocke des données dans la base ; malgrè tout quand je relance l'appli les données ont bien été récupérées.
Mon problème est résolu : Core Data n'est pas multithreading... J'ai déplacé toute ma logique "modèle de données" dans le thread principal.
Merci Larme et Benjo pour m'avoir mis sur la piste
Il y a un passage sur ce sujet dans la doc "Concurrency with Core Data" qui explique les limitations et les approches conseillées.
En pratique ces recommandations sont vagues et n'offrent pas vraiment de solution. Certaines personnes ont planché sur le problème. Notamment Magical Record gère cette problématique, il me semble.
Oui, j'ai lu pas mal de choses ; il faudrait que j'instancie deux NSManagedObjectContext, un pour le thread principal et un autre pour celui en arrière plan, mais c'est assez compliqué à gérer, a priori ça ne justifie pas vraiment dans mon cas.
Comme ça le code sera automatiquement exécuté sur la dispatch_queue adéquate et cela t'évitera ce genre de soucis.
De plus tu peux très bien indiquer le type de concurrency que tu veux quand tu crées ton MOC (PrivateQueue, MainQueue, ...).
Si besoin également tu utiliser les concepts de parentContext pour créer une sorte de hiérarchie de MOC : un MOC utilisant une private queue pour tes opérations de traitement un peu longues, auquel tu affectes un MOC parent utilisant lui la mainQueue, et qui lui a comme parent ton PersostantStore (la base sqlite sur le disque quoi). Le MOC fils suave sur le MOC père qui se sauve sur le PersistantStore cela permet de décharger le mainThread tout en gardant une bonne consistance.
Ou encore carrément plus simple, tu utilises MagicalRecord et ça te fait tout ça tout seul (et tu n'as même pas à te gérer tes MOC puisqu'il gère les siens en interne de façon transparente)
Merci pour ces conseils, mais c'est ma première appli, tu vas me noyer 8--)
J'y vais par petites briques...
J'avais effectivement vu que MagicalRecord facilitait l'utilisation de Core Data, mais bon, pour le moment je préfère limiter quand c'est possible l'utilisation de librairies externes. D'ailleurs je suis en train d'enlever AFNetwork, qui ne se justifie pas vraiment pour ce que j'en fait.
T'es sûr que c'est pas le contraire ? :-*
Le père est un private et le fils est un main.
Et pour moi la seule contrainte qu'on pourrait avoir et qui impose d'avoir un MOC qui soit absolument sur le main thread c'est sur le MOC un peu particulier qu'est le MOC racine, celui dont le "parent" est le PSC et non un autre MOC.
Par exemple si on a une hiérarchie de MOCs comme ceci :
PSC <--- MOC1 <--- MOC2 <--- MOC3 <--- MOC4
'- MOC5 <--- MOC6 '- MOC7
Alors seul MOC1 a une contrainte particulière puisqu'il est lié au PersistantStoreCoordinator et donc à la base sqlite3 sur le disque, les autres sont un peu tous dans la même configuration les uns les autres (ils ne sont pas liés à un PSC mais à un MOC parent).
Tu fais un "save:" sur MOC3, il va juste appliquer les modifications que tu as faites dans MOC3 sur son parent MOC2 ("merge", un peu quand tu merge une branche en GIT dans sa branche parente quoi). Alors que tu fais un "save:" sur MOC1, il va sauver sur la base sur le disque.
S'il y a bien un MOC qui sort du lot et n'est pas comme les autres et nécessiterait éventuellement une contrainte telle que de devoir être exécuté sur la mainQueue, c'est bien celui-ci.
Le paragraphe "Saving in a Background Thread is Error-prone" de la doc va également dans ce sens, indiquant que faire un "save:" sur un background-thread peut corrompre la base si jamais l'application est killée alors que le background thread n'était pas fini (la sauvegarde des données ne serait alors que partielle, menant à une base potentiellement corrompue). Alors que si tu le fais dans le main thread, l'appli ne va pas quitter avant d'avoir fini la RunLoop du mainThread et donc va finir son "save:". Si l'appli est killée et le "save:" d'un thread secondaire interrompu, c'est pas trop grave car seul la sauvegarde du MOC3 dans MOC2 par exemple sera foireuse, mais cet état foireux ne sera pas répercuté sur le PSC et donc sur la base sur le disque. Donc au prochain démarrage de l'appli tu repartiras de l'état de ton PSC / ta base qui n'aura pas été corrompue, elle, donc c'est OK.
Perso je ferais plutôt comme ça :
MOC 1 (private queue) <
MOC 2 (main thread)
et pour éviter le problème soulevé dans "Saving in a Background Thread is Error-prone", je m'arrangerai pour que la méthode save: sur MOC1 soit toujours appelée depuis le main thread, avec un block qui s'exécute à la fin pour autoriser l'appli à quitter.
Imagine que ton persistentStore soit connecté à une URL distante. Si tu sauves sur le mainThread, ne vas-tu bloquer le mainThread ?
- Si tu t'arranges pour que la méthode "save:" sur MOC1 soit toujours appellée depuis le main thread, quel est l'intérêt de lui associer une private queue finalement ?
- Avec ta solution, si ton PersistentStore est connecté à une URL distante et que tu viens de me dire que tu exécutes le "save:" sur le mainTread, bah toi aussi tu vas bloquer le main thread, donc ta solution ne change rien à cet éventuel problème ^^
Non, car je peux implémenter une méthode (c'est du pseudo-code)
Du coup ça n'a pas trop de sens ta solution : en plus de ne pas faire ce que tu décris dans le message plus haut, ta pseudo-méthode blockTheApp qui empêcherait l'appli de quitter tant qu'elle n'est pas débloquée, non seulement n'est pas exception-safe, mais en plus la seule manière de l'implémenter serait via une attente passive (pour laquelle tu mériterais alors des coups de fouet, ça va de soi)..
Et en plus de tout ça, faire tout ce que tu décris là revient à se compliquer la vie pour faire exactement ce que les parentContext savent déjà faire, et ce qu'implémente déjà MagicalRecord, à savoir :
- avoir un rootContextForSaving qui se base sur le mainThread et ne sert qu'à ça (qu'à sauver ses modifications dans le PSC via le main thread, mais jamais rien d'autre, jamais aucune création d'entité n'est faite directement sur ce MOC en général par exemple)
- avoir un defaultContext, dont le rootContextForSaving est le parent, qui se base sur une privateQueue, et sur lequel on vient faire nos créations/modifications/suppressions d'entités, qui sont ainsi faites en background
- Quand on veut sauver nos données sur le PSC:
- on fait un "save:" sur le defaultContext, qui se contente finalement juste de faire un "merge" de ce defaultContext dans son contexte parent rootContextForSaving (or un merge sur un MOC sur lequel on ne fait jamais directement de création/modif/suppression, c'est assez direct et ne nécessite pas de résolution de conflit, c'est un peu comme quand on merge une branche GIT sur sa branche parente alors qu'on n'a fait aucune modification sur la branche parente sur laquelle on merge depuis qu'on a démarré la branche), donc au final c'est très peu coûteux
- On fait ensuite un "save:" sur le rootContextForSaving pour qu'il sauvegarde ces modifications sur le disque. En général soit on ne fait ça que quand l'appli passe en arrière plan (et risquerait ainsi d'être killée à tout moment pendant qu'elle est en background) ou ce genre de chose " ce qui a l'avantage de ne pas bloquer le main thread à chaque save: qu'on veut faire pendant que l'appli tourne, mais a l'inconvénient de garder tout pour la fin et d'avoir un applicationWillResignActive:/applicationDidEnterBackground: pententiellement (trop) long " soit on le fait régulièrement, pour faire "plein de petits commits rapides plutôt qu'un gros long à la fin"
Bref au final ce pattern est connu " Apple a aussi créé le principe des parentContext et le concept de hiérarchies de MOC pour ça après tout, comme ils expliquent " et est aussi éprouvé " puisque mis en pratique par MR et que ça fonctionne très bien.Pourquoi réinventer la roue là où on peut utiliser ce concept de hiérarchie de MOC pour faire ça de façon à la fois + simple et + sécurisante ?
Salut Ali,
en fait ce que je voulais dire dans mon message #15, c'est que tu n'as pas accès au parentContext, c'est une @property "privée". Mais en revanche, tu as accès via ton .h à deux choses:
-> @property NSManagedObjectContext * managedObjectContext
-> - (void)saveToStore
Donc en fait, si j'utilise bien les parentContext.
Peux-tu être plus explicite (attente passive) ?
En fait, j'ai l'impression qu'on n'a pas les mêmes paradigmes en tête. Tu parles d'iOS, mais en fait c'est vrai que j'ai plus OSX en tête (oui c'est mal, on est dans un thread iOS...). Le saveToRoot est appelé par l'utilisateur quand il vaut sauver, et il ne veut surtout pas que ça bloque le mainThread.
D'ailleurs, je crois savoir qu'on n'a pas le droit de bloquer le mainThread sur iOS, et ta façon de faire peut bloquer le mainThread, non ?
En fait, ce que j'expose ici est d'après ce que je crois avoir compris de ce projet (tu peux jeter un oe“il, il n'y a qu'une classe) : https://github.com/karelia/BSManagedDocument
Un article intéressant sur les différentes manières de relier les MOC en multithread.
Je crois que les deux approches discutées ici sont comparées avec la 3eme qui consiste à ne pas utiliser les nested context.
Très intéressant ! Merci du lien, qui montre que mon approche n'est pas idiote ;-)