- ou 10 leçons de CoreData apprises au combat. appris de CoreData
- CoreData est un cadre de gestion de graphe d’objets
- CoreData peut stocker les données dans différents formats
- Un couple de définition de termes
- Use NSPersistentContainer
- Utiliser les relations
- Évitez le code stringly autant que possible
- Gardez votre NSPersitentContainer.viewContext en lecture seule
- Watch those threads
- Utiliser l’argument de lancement pour suivre le contexte et le threading
- Utiliser des requêtes agrégées
- Garder le code lié aux données de base dans des catégories
- Adoptez un modèle de transaction pour vos modifications de vos données
- Ne sauvegardez pas le contexte à partir d’une méthode de catégorie de modèle
ou 10 leçons de CoreData apprises au combat. appris de CoreData
J’ai utilisé CoreData sur de multiples projets durant ma carrière de développeur sur la plateforme Apple (la première fois, c’était dans MacOS 10.5…), et au fil des ans, j’ai appris des choses à la dure (aka : « frapper ma tête contre le mur pendant beaucoup trop longtemps »). Je me suis donc dit que j’allais partager mon apprentissage avec vous. Avec un peu de chance, je vous épargnerai quelques bleus sur le front.
Notez que ce n’est pas un guide pratique pour la configuration de CoreData. Il est destiné à être un article avec quelques bonnes pratiques que vous pouvez ramasser pour vos projets.
Voici l’aperçu rapide d’Apple : « Utilisez Core Data pour enregistrer les données permanentes de votre application pour une utilisation hors ligne, pour mettre en cache les données temporaires et pour ajouter une fonctionnalité d’annulation à votre application sur un seul appareil. »
Pour donner un peu plus de détails, CoreData est la technologie d’Apple pour enregistrer localement vos données structurées. Vous utilisez l’éditeur de modèle pour définir votre structure de données, tout comme un éditeur de schéma de base de données, puis vous pouvez utiliser ces objets dans votre code pour stocker vos données.
CoreData est un cadre de gestion de graphe d’objets
Ce qui signifie que lorsque vous créez votre modèle, vous définissez vos objets et leurs relations. Par exemple, si vous avez une entité Société et une entité Employé dans votre modèle de données, ces deux entités seront liées par une relation qui relie ces deux entités. Probablement nommée employees
sur l’entité Company
et employer
sur l’entité Employee
. Vous ajoutez simplement la propriété de relation sur les entités et c’est tout. Vous n’avez pas besoin de créer une table de jointure comme dans une base de données.
La beauté de CoreData par rapport à une base de données traditionnelle est que lorsque vous faites une requête pour obtenir la société X, vous pourrez obtenir les employés directement en accédant simplement à company.employees
. Pas besoin de construire une requête de jointure ou de réinterroger.
CoreData peut stocker les données dans différents formats
Oui, vous avez bien entendu, CoreData peut enregistrer vos données dans différents formats/lieux. Chacun a ses avantages et ses inconvénients.
- Magasin binaire
- Magasin SQLite
- Magasin en mémoire
Vous pouvez lire cet article pour obtenir les avantages et les inconvénients de chacun. J’utilise toujours SQLite, mais je pourrais voir comment un magasin en mémoire serait super rapide.
Un couple de définition de termes
NSManagedObject
: c’est la classe de base représentant les objets que vous avez stockés dans CoreData.
Demande de récupération : c’est l’équivalent d’une requête dans le monde de CoreData. Elle est représentée par la classe NSFetchedObject
.
Use NSPersistentContainer
Jusqu’à iOS 10.0, la mise en place de la pile CoreData impliquait pas mal de code passe-partout. Vous deviez vous débrouiller tout seul pour définir la stratégie à utiliser pour traiter l’utilisation multithread de CoreData.
Avec NSPersistentContainer
, Apple encapsule la plupart du code passe-partout. Vous épargnant le débat sur la meilleure façon de configurer votre pile pour des performances optimales.
Cet article contient toutes les informations dont vous avez besoin pour l’utiliser correctement.
Utiliser les relations
Je mentionne toujours ceci parce qu’à deux occasions, pour des projets dont j’ai hérité, le modèle CoreData était configuré comme une base de données. Aucune relation n’était définie. Au lieu de cela, l’ID de l’objet lié était stocké et donc une requête supplémentaire de récupération était nécessaire pour obtenir l’objet lié. Je pense que beaucoup de développeurs abordent CoreData comme une base de données et commettent cette erreur. Cela finit par rendre l’ensemble du projet beaucoup plus complexe et beaucoup plus lent.
Suivez cet article pour commencer.
Évitez le code stringly autant que possible
Le code stringly fait référence au code qui utilise des chaînes de caractères pour accéder aux données. Sur iOS, vous disposez de quelques API de type stringly : UserDefaults
, CoreData
sont quelques-unes qui me viennent à l’esprit.
Si nous revenons à l’exemple de la société, voici deux options pour créer une société et définir son nom :
//stringly version to create and edit a company
let company = NSEntityDescription.insertNewObject(forEntityName: "Company", in: managedObjectContext)
company.set("my less than great corporation", forKey: "name")//non stringly version
let company = Company(in: managedObjectContext)
company.name = "My super corporation"
À première vue, ces deux options fonctionnent, elles compilent, il n’y a rien de mal avec celles-ci…
Le problème avec le code stringly est que le compilateur ne peut pas vous aider. Imaginez que vous ayez besoin d’accéder à la name
de l’entreprise à plusieurs endroits dans le code, et de manière réaliste, vous en aurez besoin d’au moins deux : un pour définir la et un pour la lire, vous devez maintenant taper cette chaîne name
à deux endroits. Bien sûr, vous pouvez créer une variable pour stocker la chaîne de caractères et utiliser cette variable, mais cela demande un effort supplémentaire, elle ne sera pas tapée vérifiée et nous savons tous que les développeurs sont paresseux…
Donc cela laisse généralement cette chaîne de caractères à plusieurs endroits. Ensuite, vous voulez la renommer et vous devez la traquer avec des recherches et des remplacements… Si vous en ratez un, votre application va planter.
Voici la liste des fonctionnalités du compilateur dont vous ne bénéficierez pas avec la syntaxe stringly :
- auto-complet
- refactor
- avertissement du compilateur en cas de faute d’orthographe
- erreur du compilateur si vous supprimez/renommez cette entité ou cette propriété dans votre modèle
Pour pouvoir utiliser la version nonstringly version du code, il vous suffira de demander au compilateur de générer des classes pour vos entités (vous pouvez suivre les instructions d’Apple dans la rubrique « Sélectionner une option de génération de code »). C’est maintenant la valeur par défaut lors de la création d’une nouvelle entité.
Gardez votre NSPersitentContainer.viewContext en lecture seule
Pour des raisons de performance, vous ne voulez pas effectuer de longues opérations sur le viewContext car il s’exécutera sur le thread principal et bloquera potentiellement l’interface utilisateur.
La solution est de prendre l’habitude de ne faire des modifications à vos objets qu’à l’intérieur d’un bloc que vous planifiez avec la méthode NSPersistentContainer.performBackgroundTask()
. Lorsque vous enregistrez vos modifications dans ce bloc, celles-ci seront fusionnées à nouveau dans NSPersitentContainer.viewContext
et vous pourrez mettre à jour votre UI.
Watch those threads
L’un des principaux drags avec CoreData est de l’utiliser d’une manière thread-safe. Bien qu’une fois que vous avez compris le concept, c’est assez facile.
Ce qui se résume à ce que tout objet de données que vous obtenez de CoreData ne peut être utilisé que sur le thread auquel le managedobjectContext
de l’objet est attaché.
Une façon de s’assurer que vous utilisez le bon thread pour accéder à votre objet est d’utiliser le NSManagedObjectContext.performBlockAndWait
qui s’assurera que votre code est utilisé sur le bon thread.
Une fois dans ce bloc, vous devrez utiliser NSManagedObject.objectID
et NSManagedObjectContext.existingObject(withId:)
pour passer l’objet à travers les threads et le contexte.
Si vous ne le faites pas, vous vous retrouverez très probablement avec des blocages. Imaginez:
- Vous planifiez une tâche depuis le thread principal avec Dans le cas où quelque chose est planifié depuis le thread principal
NSManagedObjectContext.performBlockAndWait()
. À ce stade, le thread principal est bloqué en attendant que ce bloc se termine. - Dans ce bloc, vous passez l’objet
foo
du contexte principal à la méthodebar()
et le dispatcher à un autre thread. - Pour être un bon citoyen de CoreData,
bar()
va récupérer le contexte attaché à foo et dispatcher sa tâche en utilisantfoo.managedObjectContext.performBlockAndWait()
- Boom, vous êtes deadlocké car ce contexte ne s’exécute que sur le thread principal et le thread principal attend ce complete.
Une autre convention qui aide est de passer des objets entre les contrôleurs de vue seulement s’ils existent dans le contexte du thread principal.
Utiliser l’argument de lancement pour suivre le contexte et le threading
Si vous suivez les conseils ci-dessus, cela devrait limiter les chances qu’une instance NSManagedObject
soit appelée sur le mauvais thread. Mais cela peut encore arriver. Assurez-vous donc, lorsque vous êtes en développement, d’activer l’assertion de débogage des threads
-com.apple.CoreData.ConcurrencyDebug 1
Cet article vous dira exactement comment le faire et vous parlera d’autres drapeaux que vous pouvez configurer pour plus de débogage.
Utiliser des requêtes agrégées
CoreData propose groupBy
, sum
et count
comme fonctions agrégées qui peuvent être exécutées pendant une requête. La configuration peut être un peu complexe, mais en vaut la peine.
L’alternative est de créer une requête pour obtenir tous les objets qui correspondent à vos critères, puis d’exécuter une boucle for à travers toutes les entités et de faire le calcul vous-même. Plus facile à coder, mais beaucoup moins efficace.
Lorsque vous exécutez une requête de récupération agrégée, CoreData la traduit en une requête de base de données et exécute les mathématiques directement au niveau de la base de données. Cela le rend super rapide.
Cet article est une bonne introduction à la façon de le faire.
Garder le code lié aux données de base dans des catégories
Une fois que vous avez votre sous-classe NSManagedObject
créée par Xcode, vous trouverez très probablement que vous avez du code qui devrait être dans la classe qui représente votre objet (pour calculer des valeurs, importer des données de votre serveur, etc). Mais vous avez demandé à CoreData de créer les classes pour vous…
Donc vous devrez créer une catégorie pour l’objet qui a besoin d’un comportement supplémentaire. Ne mettez que le code lié à CoreData là-dedans pour que vous puissiez le garder propre.
Adoptez un modèle de transaction pour vos modifications de vos données
Vous pouvez modifier vos objets autant que vous voulez, mais pour persister ces changements, vous devez appeler NSManagedObjectContext.save()
.
Vous vous retrouverez rapidement à oublier d’appeler save()
et vous aurez des modifications pendantes dans votre contexte. À ce stade, tous les paris sont ouverts, la prochaine fois que vous appelez save()
, vous ne serez pas sûr de ce que vous commettez.
Adoptez un modèle de transaction pour éviter de laisser un contexte sale derrière vous. Pour ce faire, créez une catégorie sur NSPersistentContainer
comme suit :
extension NSPersistentContainer {
@objc func performBackgroundSaveTask(_ block: @escaping (NSManagedObjectContext) -> Void) {
performBackgroundTask { (context: NSManagedObjectContext) in
block(context)
context.save()
}
}
}
Assurez-vous désormais que toutes les modifications que vous faites sont exécutées dans un bloc planifié avec performBackgroundSaveTask()
. De cette façon, vous avez la garantie que les modifications sont sauvegardées au moment où vous le faites.
Ne sauvegardez pas le contexte à partir d’une méthode de catégorie de modèle
N’appelez jamais save()
à partir d’une catégorie d’entité. Il n’y a rien de pire que d’appeler une méthode sur votre objet sans réaliser qu’elle va sauvegarder tout le contexte avec. En effectuant certains changements, vous ne voudrez peut-être pas sauvegarder. L’appel de save devrait être fait au moment où les modifications de l’objet sont commencées.