Plantage lors du test d'une appli (ok en débogage)

Jean-PhiJean-Phi Membre
avril 2014 modifié dans API UIKit #1

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...


Réponses

  • Il faudrait que tu récupères le log crash et le symbolize.


  • Jean-PhiJean-Phi Membre
    avril 2014 modifié #3

    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".


  • Jean-PhiJean-Phi Membre
    avril 2014 modifié #4

    Oups, j'étais pas dans le bon dossier logs...


     


    Voici le log du plantage, si cela évoque quelque chose à  quelqu'un, merci !


     



     


    Incident Identifier: 6D30ABCF-02F9-4DF3-AFFF-EAAB2CAC1B08

    CrashReporter Key:   761ff55e6ed6cd60291835e9aca80823155c869a

    Hardware Model:      iPhone6,2

    Process:             Trace de Trail [14505]

    Path:                /var/mobile/Applications/E22470A9-3672-44E8-84DF-56DB367B98DB/Trace de Trail.app/Trace de Trail

    Identifier:          Trace-de-trail.Trace-de-Trail

    Version:             1.0 (1.0)

    Code Type:           ARM-64 (Native)

    Parent Process:      launchd [1]


    Date/Time:           2014-04-19 23:30:02.934 +0200

    OS Version:          iOS 7.0.3 (11B511)

    Report Version:      104


    Exception Type:  EXC_BAD_ACCESS (SIGSEGV)

    Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000010

    Triggered by Thread:  0


    Thread 0 Crashed:

    0   libobjc.A.dylib                   0x00000001961eb9d0 0x1961e4000 + 31184

    1   UIKit                             0x000000018d142428 0x18d134000 + 58408

    2   UIKit                             0x000000018d142428 0x18d134000 + 58408

    3   UIKit                             0x000000018d142428 0x18d134000 + 58408

    4   UIKit                             0x000000018d142428 0x18d134000 + 58408

    5   UIKit                             0x000000018d142428 0x18d134000 + 58408

    6   UIKit                             0x000000018d142428 0x18d134000 + 58408

    7   UIKit                             0x000000018d142428 0x18d134000 + 58408

    8   UIKit                             0x000000018d141b9c 0x18d134000 + 56220

    9   Foundation                        0x000000018ad4fbbc 0x18acf4000 + 375740

    10  UIKit                             0x000000018d1419d0 0x18d134000 + 55760

    11  UIKit                             0x000000018d1504e8 0x18d134000 + 115944

    12  UIKit                             0x000000018d242ea4 0x18d134000 + 1109668

    13  UIKit                             0x000000018d242450 0x18d134000 + 1107024

    14  UIKit                             0x000000018d1cefb0 0x18d134000 + 634800

    15  UIKit                             0x000000018d139d84 0x18d134000 + 23940

    16  CoreFoundation                    0x000000018a2337dc 0x18a168000 + 833500

    17  CoreFoundation                    0x000000018a230a64 0x18a168000 + 821860

    18  CoreFoundation                    0x000000018a230df0 0x18a168000 + 822768

    19  CoreFoundation                    0x000000018a171b34 0x18a168000 + 39732

    20  GraphicsServices                  0x000000018fb9782c 0x18fb8c000 + 47148

    21  UIKit                             0x000000018d1b00e4 0x18d134000 + 508132

    22  Trace de Trail                    0x000000010005932c 0x10004c000 + 54060

    23  libdyld.dylib                     0x00000001967dba9c 0x1967d8000 + 15004


    Thread 1:

    0   libsystem_kernel.dylib            0x00000001968bdac8 0x1968bc000 + 6856

    1   libdispatch.dylib                 0x00000001967c1d74 0x1967bc000 + 23924


    Thread 2 name:  AFNetworking

    Thread 2:

    0   libsystem_kernel.dylib            0x00000001968bdcc0 0x1968bc000 + 7360

    1   CoreFoundation                    0x000000018a232ca8 0x18a168000 + 830632

    2   CoreFoundation                    0x000000018a230e38 0x18a168000 + 822840

    3   CoreFoundation                    0x000000018a171b34 0x18a168000 + 39732

    4   Foundation                        0x000000018ad05590 0x18acf4000 + 71056

    5   Foundation                        0x000000018ad63148 0x18acf4000 + 454984

    6   Trace de Trail                    0x000000010007b3f0 0x10004c000 + 193520

    7   Foundation                        0x000000018adec76c 0x18acf4000 + 1017708

    8   libsystem_pthread.dylib           0x00000001969581ac 0x196954000 + 16812

    9   libsystem_pthread.dylib           0x0000000196958104 0x196954000 + 16644

    10  libsystem_pthread.dylib           0x00000001969557ac 0x196954000 + 6060


    Thread 3 name:  com.apple.NSURLConnectionLoader

    Thread 3:

    0   libsystem_kernel.dylib            0x00000001968bdcc0 0x1968bc000 + 7360

    1   CoreFoundation                    0x000000018a232ca8 0x18a168000 + 830632

    2   CoreFoundation                    0x000000018a230e38 0x18a168000 + 822840

    3   CoreFoundation                    0x000000018a171b34 0x18a168000 + 39732

    4   Foundation                        0x000000018ad5e7f8 0x18acf4000 + 436216

    5   Foundation                        0x000000018adec76c 0x18acf4000 + 1017708

    6   libsystem_pthread.dylib           0x00000001969581ac 0x196954000 + 16812

    7   libsystem_pthread.dylib           0x0000000196958104 0x196954000 + 16644

    8   libsystem_pthread.dylib           0x00000001969557ac 0x196954000 + 6060


    Thread 4 name:  com.apple.CFSocket.private

    Thread 4:

    0   libsystem_kernel.dylib            0x00000001968d676c 0x1968bc000 + 108396

    1   libsystem_pthread.dylib           0x00000001969581ac 0x196954000 + 16812

    2   libsystem_pthread.dylib           0x0000000196958104 0x196954000 + 16644

    3   libsystem_pthread.dylib           0x00000001969557ac 0x196954000 + 6060


    Thread 5:

    0   libsystem_kernel.dylib            0x00000001968d6e74 0x1968bc000 + 110196

    1   libsystem_pthread.dylib           0x00000001969557a4 0x196954000 + 6052


    Thread 6:

    0   libsystem_kernel.dylib            0x00000001968d6e74 0x1968bc000 + 110196

    1   libsystem_pthread.dylib           0x00000001969557a4 0x196954000 + 6052


    Thread 7:

    0   libsystem_kernel.dylib            0x00000001968d6e74 0x1968bc000 + 110196

    1   libsystem_pthread.dylib           0x00000001969557a4 0x196954000 + 6052


    Thread 8:

    0   libsystem_kernel.dylib            0x00000001968d6e74 0x1968bc000 + 110196

    1   libsystem_pthread.dylib           0x00000001969557a4 0x196954000 + 6052


    Thread 0 crashed with ARM Thread State (64-bit):

        x0: 0x0000000154e10530   x1: 0x000000018d7c9c60   x2: 0x000000018d7e3116   x3: 0x000000019690fd18

        x4: 0x0000000000000010   x5: 0x0000000000000000   x6: 0x3a726f6c6f43746e   x7: 0x0000000000000000

        x8: 0x00000001973a49f0   x9: 0x0000000000000000  x10: 0x00000001550a9e00  x11: 0x00000129000001ff

       x12: 0x00000001550ab1f0  x13: 0x5000000000000000  x14: 0x0000000000000079  x15: 0x0000000196bd8e20

       x16: 0x00000001961eb9c0  x17: 0x000000018d142a44  x18: 0x0000000000000000  x19: 0x0000000154e43090

       x20: 0x0000000154e0c4c0  x21: 0x0000000000000000  x22: 0x000000018d7e3116  x23: 0x00000001973e5000

       x24: 0x000000017803dca0  x25: 0x000000018d7c980a  x26: 0x000000018d7c9f25  x27: 0x0000000000000002

       x28: 0x0000000154e43090  fp: 0x000000016fdb1760   lr: 0x000000018d237314

        sp: 0x000000016fdb1720   pc: 0x00000001961eb9d0 cpsr: 0x20000000


    ...



  • 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.




  • 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


  • CéroceCéroce Membre, Modérateur
    avril 2014 modifié #8

    Mon problème est résolu : Core Data n'est pas multithreading...

    Pas tout à  fait. En fait, on ne peut pas partager un NSManagedObjectContext entre deux threads.
    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.


  • Pas tout à  fait. En fait, on ne peut pas partager un NSManagedObjectContext entre deux threads.

    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.

  • AliGatorAliGator Membre, Modérateur
    Utilise la méthode performBlock: ou performBlockAndWait: de NSManagedObjectContext pour exécuter tes tâches CoreData dépendantes d'un contexte.


    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.



  • 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). 




     


     


    T'es sûr que c'est pas le contraire ?  :-*


    Le père est un private et le fils est un main. 

  • AliGatorAliGator Membre, Modérateur
    avril 2014 modifié #13
    Bah quand tu fais un "save:" sur un MOC il fait une sorte de "merge" entre lui-même et son parent. Donc ça remonte la hiérarchie. Dans le cas du MOC "racine" son parent c'est le PSC donc au final le fichier sur le disque.

    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.
  • colas_colas_ Membre
    avril 2014 modifié #15

    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 ?


  • AliGatorAliGator Membre, Modérateur
    Je ne comprend pas trop ta solution.

    - 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)



    - (void)saveToStore
    {
            [NSApp.delegate blockTheApp] ;
            [self.parentMOC performBlock:^
    {
         [self.parentMOC save:nil] ;
         dispatch_async_on_main_queue(^{
          [NSApp.delegate unblockTheApp] ;
          }) ;
    }] ;
    }
  • AliGatorAliGator Membre, Modérateur
    Heu avec le pseudo-code de ton dernier message #17 tu ne sauves pas du tout ton parentMOC sur le main thread là , contrairement à  ce que tu dis faire dans ton message #15... Au contraire tu appelles le "save:" dans le "performBlock:" appelé sur ton self.parentMOC, block qui par définition va s'exécuter sur la queue associé audit MOC. Et donc à  la private queue, puisque le MOC1 en question a une private queue d'après ce que tu décris en #15.

    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.


     




    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)..




     


    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

  • AliGatorAliGator Membre, Modérateur
    Je voulais dire attente active bien sur et pas passive ;-)
  • 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 ;-)


Connectez-vous ou Inscrivez-vous pour répondre.