[Rà‰SOLU] Optimisation des sauvegardes CoreData

BenjoBenjo Membre
juillet 2013 modifié dans Dev. iOS, watchOS, tvOS #1

Bonjour à  tous


 


Dans mon projet j'ai un tableView dont le dataSource est un NSArray. Pour sauvegarder mes données, j'utilise CoreData. Seulement, je met souvent à  jour mes données dans mon app et pour la sauvegarde, j'efface toutes les données puis je les re-sauvegardes. Seulement, quand mon NSArray est beaucoup remplit, la sauvegarde est un peu longue. Je ne pense pas que ce soit une bonne solution...


J'aimerais bien optimiser cette sauvegarde et j'ai quelques idées :


 


• Sauvegarder seulement ce qui change.


• Effectuer la sauvegarde lorsque l'application est fermée donc utiliser la méthode "applicationWillTerminate".


 


Pour moi, la deuxième méthode serait la meilleure. Mais je ne sait pas comment aller chercher les données de mon NSArray depuis l'AppDelegate dans ma classe nommée "ViewController".


 


Avez-vous une idée sur les questions ?


 


Merci d'avance :)


Mots clés:

Réponses

  • AliGatorAliGator Membre, Modérateur
    1) bah oui pourquoi tout effacer et tout re-sauver alors que sauver que ce qui change est 100x plus efficace ? En plus CoreData est fait pour ça, il sait les objets qui ont changé dans ton MOC et quand tu fais "-save:" sur ton MOC il ne sauve que ce que tu as modifié justement (et encore heureux), gérant tout seul les modifications, ajouts et suppressions...


    Donc c'est définitivement ça qu'il fait faire !!! Tu vas grandement y gagner et en plus c'est prévu pour.


    2) Utilise plutôt la NSNotification associée à  cet événement plutôt que la méthode de ton UIApplicationDelegate, comme ça tu pourras mettre ton code de sauvegarde où ça t'arrange et où cela est le plus logique pour respecter le MVC et l'archi de ton appli.
  • L'événement applicationWillTerminate a deux défauts :


    - un gros défaut : il peut ne pas être déclenché lorsque l'application quitte


    - un petit défaut : les opérations lancées sur cet événement doivent durer moins de 5 secondes, donc attention car "... la sauvegarde est un peu longue."


  • Merci pour vos réponses. Donc je vais m'orienter vers la sauvegarde lorsqu'il y a du changement. Merci aussi à  jpimber pour ces infos ça pourra m'être très utile pour mes prochaines apps.


  • AliGatorAliGator Membre, Modérateur

    Donc je vais m'orienter vers la sauvegarde lorsqu'il y a du changement. .

    ce qui consiste en pratique à  faire un simple [managedObjectContext save:&error] puisque CoreData gère tout ça tout seul, donc ça ne devrait pas être trop difficile (si bien sur tu manipules tes NSManagedObject ditectement et que tu n'as pas eu l'idée saugrenue de dédoubler tes entités avec tes propres sous-classes de NSObject et copier les infos dedans plutôt que de manipuler les NSMO directement)

  •  


     


    si bien sur tu manipules tes NSManagedObject ditectement et que tu n'as pas eu l'idée saugrenue de dédoubler tes entités avec tes propres sous-classes de NSObject et copier les infos dedans plutôt que de manipuler les NSMO directement

    Qu'est-ce que tu entends par dédoubler tes entités ? Je n'ai pas bien compris cette partie là .




  • Qu'est-ce que tu entends par dédoubler tes entités ? Je n'ai pas bien compris cette partie là .




    Disons que tu as une entité Person et tu décides de créer une classe Person dérivée directement de NSObject.

  • AliGatorAliGator Membre, Modérateur
    juillet 2013 modifié #8
    Voilà , exactement. J'ai déjà  vu certains développeurs qui avaient dans le modèle CoreData une entité Person, ils généraient la classe Person associée avec Xcode (classe dérivant de NSManagedObject donc, et directement gérée par CoreData donc avec toute la magie qui va avec)... mais créaient aussi en plus un fichier PersonObj.h pour déclarer une classe PersonObj dérivant de NSObject, et à  chaque fois qu'ils récupéraient un NSManagedObject CoreData de type "Person" (donc une instance de la classe Person) s'amusaient à  créer une instance de PersonObj et à  recopier toutes les propriétés de l'objet Person dedans, et manipulaient au final leur objet PersonObj. Ou bien créaient un NSDictionary et recopiaient toutes les propriétés de leur objet Person dans ce NSDictionary et manipulaient le dico plutôt que l'objet Person CoreData.

    J'ai jamais trop compris pourquoi certains faisaient ça et dupliquaient leurs structures plutôt que de manipuler directement les NSManagedObject, peut-être que manipuler les NSMO dans le code après les avoir récupéré leur faisaient peur, je sais pas trop... ou peut-être qu'ils croyaient que ça allait modifier directement la base de données CoreData s'ils changeaient les propriétés de leur objet (alors que ça ne le fait que quand tu fais un save sur le MOC relié au PersistantStore)... Bref ça vient certainement d'une incompréhension mais dans tous les cas ça n'a pas d'intérêt et ça complexifie la chose pour rien, surtout au niveau de la récupération et de la sauvegarde de données, ne permettant plus à  CoreData de détecter les objets que tu as ajouté/modifié/supprimé automatiquement et perdant plein d'avantages de CoreData.

    Alors que si tu manipules directement les NSMO dans ton code (pour remplir tes TableView, passer les objets à  ta vue détail), ça te facilite grandement les choses.
    En plus, avec les MOC qui peuvent avoir des parentContext maintenant (depuis 10.7 / iOS5), cela facilite aussi les choses si tu as besoin d'avoir un contexte d'édition local, dans lequel tu peux faire des modifications, puis soit les valider (si l'utilisateur clique sur OK) et du coup fusionner ces modifs avec le MOC principal, soit les laisser tomber dans l'oubli (si l'utilisateur clique sur Annuler) en ne les fusionnant pas et laissant ton contexte d'édition se détruire sans appliquer ces modifications sur son MOC parent.
  • Merci pour à  vous deux pour ces explications c'est beaucoup plus claire désormais !


    Donc pour reprendre l'exemple, j'ai une entité Person et j'ai créer une classe Person dérivée de NSManagedObjectContext (on clique, dans le modèle CoreData, sur l'entité > Editor > Create NSManagedObject Subclass). Ensuite je récupère les données dans un NSArray avec :



    monArray = [self.managedObjectContext executeFetchRequest:laRequete error:&erreur];

    C'est bien cela non ?


  • Oui et non. Tu peux bien sûr gérer un array via une requête comme tu l'écris. Mais si tu es dans une table view, tu peux surtout utiliser les apis optimisées pour ça : voir cette doc. Le code proposé dans cette doc peut quasiment s'utiliser tel que.


  • Merci pour cette référence Kubernan ça pourra m'être bien utile puisque dans mon projet j'utilise un tableView.


    Et pour faire un update, il suffit simplement de faire :



    Person *per = [unArray objectAtIndex:unArray.count - 1];

    // On modifie les attributs
    per.nom = @Benjo

    [self.managedObjectContext save:nil];

    C'est cela ?


  • Et un petit refresh table non ?


  • Oui bien sûr le refresh du tableView mais cela dépend du projet après.


  • AliGatorAliGator Membre, Modérateur
    juillet 2013 modifié #14
    Oui c'est ça. Sauf qu'avec les NSFetchedResultController c'est encore plus optimisé et efficace car :


    - tu peux être informé en live quand un objet est ajouté/modifié/supprimé de ton jeu de données et ainsi animer juste les rows de ta tableView correspondantes, ce qui permet à  la fois d'utiliser des animations d'insertion/suppression sympas et d'éviter de faire un reloadData complet

    - le NSFRC ne récupère que les données utiles. Si tu as 1000 lignes dans ton résultat de requête, le NSFRC ne va te récupérer par exemple que les 100 premiers car de toute façon dans ta tableView au départ tu ne vas certainement n'en afficher que 5 ou 10 mais pas 1000 sur un écran. Par contre c'est quand tu vas scroller ta tableView que tu vas afficher les suivants. Quand tu vas arriver aux alentours disons du 80e, le NSFRC va voir que tu te rapproches des 100 et qu'il va lui falloir très probablement la suite si tu continues à  scroller, donc il va demander les 100 suivants à  CoreData pour que quand tu auras scrollé jusque là  il puisse t'afficher la suite. Et ainsi de suite.

    En résumé, il gère automatiquement la pagination des données, pour t'éviter d'avoir des requêtes trop lourdes (genre qui mettent trop de temps à  s'exécuter car tu as trop de résultat d'un coup, et en plus qui prennent de la place si tu gardes tous les 1000 résultats dans un gros tableau utilisant plus de mémoire que le nombre d'objets dont tu as effectivement besoin pour l'affichage...

    - sans parler qu'il gère aussi un cache local de données pour accélérer encore plus les fetches.



    Du coup plus besoin d'un NSArray pour garder en mémoire tous les objets que tu veux afficher dans ta tableView. Tu demandes directement au NSFRC l'objet à  l'indexPath X et il se charge du reste, de le retourner s'il l'a déjà  dans son cache, le récupérer et le mettre en cache sinon, vider le cache quand il n'est plus nécessaire, paginer les résultats pour accélérer le tout...


    En bref, je t'invite à  te pencher dessus au c'est très efficace et te facilite la vie une fois que tu as compris comment ça marche.
  • Ha oui c'est une très bonne chose pour optimiser la rapidité de l'app avec CoreData. Il va falloir que je m'y mette rapidement. Merci pour toutes ses explications AliGator c'est bon à  savoir.


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