CoreData et design pattern pour du chargement asynchrone avec blocks

Salut la compagnie,



Je viens de finir de donner une formation assez poussé où on est aller de l'optimisation des relations client / serveur à  une intro à  l'OpenGL en passant par du CoreData, le tout sur le thème de la performance.



Cela m'a donner l'occasion de rejouer sur les terres de CoreData en mode trick and tips et d'arriver de nouveau sur les problématiques de merde des UID temporaire et des NSManagedObject qui périme lorsqu'on fait un save...



Cela fait un moment que je n'ai pas utilisé CoreData en tant que développeur client (mon dernier gros usage de CoreData était pour jouer avec les NSAtomicStore et NSIncrementalStore) aussi je serais intéresser par un feedback des usages quotidiens de la chose (qui se reconnaà®trons).



Le cas est simple, je fait une mise à  jour sur un service REST qui me donne les informations de base d'une lise d'objet. Le téléchargement se fait en asynchrone dans une queue secondaire puis je repasse en thread principal pour effectuer la création d'objet CoreData.



Lors de cette création je lance le téléchargement de ressource complémentaire en arrière plan et réutilise le même principe pour repasser en premier plan pour enregistrer ces nouvelles données.



Le tout utilisant des blocks et donc des NSManagedObject capturé au lancement de la tâche, il peut se passer quelques secondes avant que le block de complétion ne soit appelé et que l'objet soit utilisé, suffisamment pour qu'un save ait pu arriver...



Je me demande donc, comment faites vous aujourd'hui pour utiliser la puissance des blocks et de GCD pour faire du 100% asynchrone tout en utilisant CoreData ?

Réponses

  • Salut !



    Ca va peut-être te donner une piste de recherche. J'essaie une solution basée sur les nouveautés Core Data introduites avec iOS 5 (doivent-être dispo sur OS X j'imagine j'ai même pas regardé). C'est assez mal documenté en fait : les nested context et les nouvelles queues pour les context.



    L'ensemble permettant finalement de lier les contexts entre eux (parent et child context) et de faire de l'asynchrone (performBlock:).

    La doc apple est somme toute assez pauvre sur le sujet je trouve et il n'y a d'ailleurs aucun sample code là  dessus à  ma connaissance.



    Ma problématique est un peu différente de la tienne mais en global j'ai besoin de traitement asynchrone et j'utilise les block du coup (bien que cela risque de me poser un problème si j'ai besoin d'arrêter les traitements comme bon me semble).



    Mon appli (de test) possède un context principal initialisé avec une queue de type NSMainQueueConcurrencyType qui comme son nom l'indique est toute indiquée pour être utilisée dans les UIViewController par exemple. L'avantage dans mon cas c'est qu'on peut réaliser un performBlock sur ce context depuis un traitement en background.



    Pour mes traitements en background j'utilise bien entendu un context dédié mais celui-ci est initialisé avec une queue de type NSPrivateQueueConcurrencyType. Ici le context possède sa propre queue.



    Ainsi lorsque je réalise un traitement en background je code ainsi : (tapé directement ici)
    <br />
    // Creation d&#39;un context pour mon traitement en arriere plan<br />
    NSManagedObjectContext *contextBackground = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];<br />
    // J&#39;associe ce context en tant qu&#39;enfant de mon context principal utilisé pour mes view controllers<br />
    contextBackground.parentContext = monContextPrincipal;<br />
    [contextBackground performBlock:^{<br />
    // Je realise ici tout mon traitement asynchrone dans la queue de mon context<br />
    // Pendant tout ce temps là , mon context parent (principal) n&#39;est pas informé des modifications dans ce context<br />
    // ....<br />
    // Mon traitement se termine enfin, je propage alors les changements vers le context principal (ce qui va automatique réveiller mes fetched results controllers)<br />
    if (&#33;[contextBackground save:NULL]) { // sauvegarde enfant -&gt; parent<br />
    }<br />
    // Et la sauvegarde permanente ?<br />
    // Elle n&#39;est possible que via le context parent (principal)<br />
    // Je peux appeler d&#39;ici le context parent (et j&#39;utilise un performBlock: (ou performBlockAndWait:) pour m&#39;assurer que les opérations de mon block se font bien dans la bonne queue<br />
    [monContextPrincipal performBlock:^{ // Sauvegarde asynchrone<br />
      if (&#33;[monContextPrincipal save:NULL]) {<br />
    }<br />
    }];<br />
    }];<br />
    




    Suis pas sûr d'avoir encore capté toutes les subtilités des nested context et leur queue mais ce pattern fonctionne assez bien pour le moment. Sauf que j'ai encore par moment des gels de mon interface quand par exemple j'ai de très gros volumes sauvegardés en arrière plan.

    À y réfléchir, c'est assez logique puisque c'est mon context principal (main queue) qui est lié à  mon persistent store.

    Je me demande si on peut pousser le vice un peu plus loin : mon context principal ne serait plus parent mais enfant d'un autre context (avec private queue) et spécialement dédié pour l'écriture sur disque. C'est ce que je suis en train d'essayer.



    Pour le moment je n'ai pas eu besoin de m'inquiéter des ID temporaires, c'est ce qui me chiffonne un peu même si j'ai pu réaliser des suppressions d'objets depuis mon UI alors qu'ils étaient en cours de création en background....



    Allez... un bon café.
Connectez-vous ou Inscrivez-vous pour répondre.