Nous allons maintenant nous attaquer au gros morceau de ce cours : Core Data !
Vous allez voir que cette technologie de persistance est très puissante et très agréable à utiliser, car elle ressemble à ce que vous connaissez déjà : l'orienté objet.
Elle est très répandue, et très rares sont les applications qui ne sont pas munies de Core Data ou d'une solution équivalente. Donc vous risquez de vous y frotter assez vite en tant que développeur iOS professionnel.
Core Data est une base de données locale, munie d'une API orientée objet.
OK... Donc le prof ne fait même plus d'effort pour donner des définitions claires...
Vous êtes dur. Reprenons lentement, Core Data est :
une base de données : cela veut dire que son rôle est de stocker des données d'une façon claire et organisée. Concrètement, quand vous utilisez Core Data, les données sont stockées dans une base de données SQLite ;
locale : cela signifie évidemment que les données sont stockées sur le téléphone et pas sur un serveur distant, cloud ou autre ;
munie d'une API orientée objet : cela signifie que vous n'allez pas manipuler SQLite directement. À la place, Core Data transforme tout ça en de jolis objets Swift que vous allez pouvoir manipuler facilement.
Concrètement, voici comment Core Data fonctionne :
1/ Vous allez définir des objets. Par exemple, si on faisait une application de listes de chansons, vous définiriez un objet Song avec des propriétés title
et artist
.
2/ Vous allez créer vos objets comme si vous créiez un objet Swift classique :
let song = Song()
song.title = "Attrapez-les tous !"
song.artist = "Pokemon"
3/ Vous sauvegardez votre objet.
Rien de plus simple, non ? C'est exactement comme l'orienté objet !
Évidemment, pour que tout cela fonctionne, il n'y a pas de lutin magique, mais plusieurs couches de technologies. Et pour que vous maîtrisiez Core Data, il vous faut en comprendre les rouages.
Appréhendez la stack
On parle souvent de Core Data Stack pour définir la pile de technologies utilisée par Core Data.
Cette technologie s'articule principalement sur 5 classes et 3 fichiers, et je vais vous expliquer comment tout ça fonctionne ensemble :
Classes :
NSManagedObjectModel ;
NSPersistentStoreCoordinator ;
NSManagedObjectContext ;
NSManagedObject ;
NSPersistentContainer.
Fichiers :
DataModel.xcdatamodeld ;
DataModel.momd ;
DataModel.sqlite.
NSManagedObjectModel
La première couche de Core Data, c'est celle qui permet de définir le schéma d'organisation des données. On l'appelle le modèle.
Pour faire cela, on utilise un fichier qui a pour extension xcdatamodeld
. Quand vous l'ouvrez dans Xcode, il ressemble à ceci :
C'est dans ce fichier qu'on va définir nos objets dans la base de données. Comment s'appellent-ils ? Quelles sont leurs propriétés ? Quelles sont les relations entre eux ?
C'est un peu comme si on définissait une classe.
Ensuite, quand on lance l'application, ce fichier un peu complexe est compilé en un fichier .momd
qui détient toutes les informations concernant les objets.
Ce fichier permet d'initialiser la classe NSManagedObjectModel
responsable de la définition des objets.
Le schéma suivant résume tout ceci :
NSPersistentStoreCoordinator
Ensuite intervient la deuxième couche : NSPersistentStoreCoordinator
. Cette classe est la pierre angulaire de Core Data.
C'est la seule classe qui touche directement à la base de données. Elle manipule le fichier SQLite (ou XML ou autre selon la base de données choisie). Dans ce fichier, elle stocke ou récupère des données.
Ensuite, elle se sert du modèle pour convertir les données stockées dans la base en objets. Ces objets sont ensuite passés à un contexte, dont le rôle est de les manipuler, les lire, les modifier, etc. On va revenir sur la notion de contexte.
Lorsque le contexte a terminé ses manipulations, il les renvoie au NSPersistentStoreCoordinator
qui vérifie qu'ils sont toujours conformes au modèle. Et si tout va bien, il les stocke dans la base de données.
Le NSPersistentStoreCoordinator
a donc le rôle de gérer la communication entre la base de données SQLite et le contexte, en faisant la conversion des objets. Il s'appuie sur le modèle, qui joue le rôle d'arbitre en vérifiant que les données correspondent bien aux définitions des objets.
NSManagedObjectContext
Enfin, le contexte, géré par la classe NSManagedObjectContext
, est une sorte de bloc-notes intelligent. Il réclame des objets au NSPersistentStoreCoordinator
qui les lui fournit.
Une fois récupérés, le contexte va manipuler les objets. Il peut les lire, les modifier, en ajouter de nouveaux, en supprimer. Il peut tout faire.
Ce qu'il faut bien comprendre, c'est que tout cela a lieu seulement dans le contexte.
C'est un peu comme si vous notiez certains paragraphes d'un livre dans un bloc-notes. Une fois dans le bloc-notes, vous pouvez modifier autant que vous voulez vos paragraphes, ça n'affectera pas le livre. Ensuite, une fois que vous êtes content de vos modifications, vous allez les intégrer toutes dans le livre avant de l'envoyer à votre éditeur.
Un contexte fonctionne exactement de la même façon. Il récupère quelques objets. Donc il ne manipule pas toute la base de données d'un coup, c'est beaucoup plus performant. Ensuite, il modifie/ajoute/supprime des objets. La base de données n'est pas encore affectée. Seulement une fois qu'il en est content, il envoie tout ça au NSPersistentStoreCoordinator
qui va faire la sauvegarde effective dans la base de données.
Du coup, lorsqu'un objet est modifié, sa modification est temporaire tant que le contexte n'a pas été sauvegardé.
NSPersistentContainer
Du coup, pour installer Core Data, si on résume, il faut :
Créer un fichier
DataModel.xcdatamodeld
.Récupérer la version compilée
DataModel.momd
.Initialiser une instance de
NSManagedModel
à partir de cette version compilée.Créer un fichier pour notre base de données
DataModel.sqlite
.Initialiser une instance de
NSPersistentStoreCoordinator
à partir de notre fichier `DataModel.sqlite` et de notre instance deNSManagedModel
.Récupérer un contexte de notre
NSPersistentStoreCoordinator
avec lequel on va pouvoir travailler.
Mais c'est horriiiiible !
Pas de conclusion hâtive, je vous prie. Mais j'avoue que c'est assez long. Précisément, ça prend 36 lignes de code :
import CoreData
class DataController: NSObject {
var managedObjectContext: NSManagedObjectContext
init(completionClosure: @escaping () -> ()) {
//This resource is the same name as your xcdatamodeldd contained in your project
guard let modelURL = Bundle.main.url(forResource: "DataModel", withExtension:"momd") else {
fatalError("Error loading model from bundle")
}
// The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
guard let mom = NSManagedObjectModel(contentsOf: modelURL) else {
fatalError("Error initializing mom from: \(modelURL)")
}
let psc = NSPersistentStoreCoordinator(managedObjectModel: mom)
managedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.mainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = psc
let queue = DispatchQueue.global(qos: DispatchQoS.QoSClass.background)
queue.async {
guard let docURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last else {
fatalError("Unable to resolve document directory")
}
let storeURL = docURL.appendingPathComponent("DataModel.sqlite")
do {
try psc.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: nil)
//The callback block is expected to complete the User Interface and therefore should be presented back on the main queue so that the user interface does not need to be concerned with which queue this call is coming from.
DispatchQueue.main.sync(execute: completionClosure)
} catch {
fatalError("Error migrating store: \(error)")
}
}
}
}
Voici comment on initialise la Core Data Stack. Inutile que vous reteniez tout ça, mais ça peut vous permettre de voir que tout ce que je vous ai dit précédemment a une réalité très concrète.
Bien sûr, un outil même aussi puissant que Core Data peut vite devenir repoussant si on doit écrire tout ça à chaque fois. C'est la raison pour laquelle Apple a créé avec iOS 10 une nouvelle classe : NSPersistentContainer
.
Cette classe permet de réduire l'installation de Core Data à seulement trois étapes :
Créer un fichier
DataModel.xcdatamodeld
.Instancier
NSPersistentContainer
en lui passant une String : le nom de notre fichier. DoncDataModel
, dans notre exemple.Récupérer un contexte à partir de notre instance de
NSPersistantContainer
.
Et voilà, vous avez le schéma complet de Core Data !
En résumé
Core Data est une base de données locale, munie d'une API orientée objet.
NSManagedModel
permet de définir les objets.NSPersistentStoreCoordinator
stocke et récupère les informations dans la base de données, il s'appuie sur le modèle pour les convertir en objets, et il les fournit au contexte qui les réclame.NSManagedObjectContext
manipule les objets de son côté et une fois les informations terminées, il sauvegarde les objets en les renvoyant auNSPersistentStoreCoordinator
qui va les reconvertir en utilisant le modèle et les stocker dans la base de données.NSPersistentContainer
permet de réduire l'installation de Core Data à seulement 3 étapes.
Si vous utilisez Core Data régulièrement, vous maîtriserez forcément le reste de ce que nous allons couvrir dans les prochains chapitres, car vous en aurez besoin pour faire fonctionner vos applications.
C'est un peu moins certain en ce qui concerne ce chapitre, notamment grâce à NSPersistentContainer
qui permet de cacher une bonne partie du fonctionnement de Core Data.
Mais je me permets d'insister sur l'importance de ce chapitre. Comprendre les rouages de Core Data et pas seulement savoir l'utiliser fera la différence le jour où vous serez face à des bugs. C'est ce qui fera de vous des développeurs intelligents face au code que vous rédigez.
En plus, dans la suite, je vais vraiment m'appuyer sur ce chapitre, donc assurez-vous que c'est bien compris avant de continuer.
Dans le prochain chapitre, nous allons installer Core Data en suivant les trois étapes dont nous avons parlé !