[macOS][TUTORIEL]Introduction à  CoreData

DrakenDraken Membre
juin 2017 modifié dans Dev. macOS #1

Un tuto pour macOS, j'en ai presque honte .. Je l'ai rédigé à  la demande de Patyom qui a quelques difficultés pour démarrer avec CoreData sous macOS


 


http://forum.cocoacafe.fr/topic/15258-comment-remplir-une-scrollview/?p=147678


 


De toute manière, c'est exactement la même chose sous iOS. Et cela m'a permis de comprendre quelques trucs que je n'avais pas saisi initialement.


 




Le but de cet exercice est de créer une mini-base de données pour archiver des bulletins de note scolaire. Chaque fiche contient :


- le nom d'un élève


- son âge


- sa moyenne


 


L'application permet de créer des fiches, de les afficher et de les détruire. La génération d'une nouvelle fiche se fait de manière aléatoire pour éviter un code de saisie trop compliqué.


 


Première étape : création d'une Cocoa Application pour macOS


 


 


 


Mots clés:

Réponses

  • Seconde étape, on indique à  Xcode qu'il s'agit d'une application utilisant CoreData, en cochant l'option Use Core Data.


     


     


     


     


  • Le projet généré par Xcode contient un fichier avec l'extension xcdatamodeld. C'est là  que les objets CoreData sont définis.  Dans le techno jargon cupertinien, ce sont des Entités (Entity).


     


    Pour créer une Entity, il faut :


     


    1) sélectionner le fichier .xcdatamodeld


    2) cliquer sur le bouton (+) Add Entity en bas de l'écran


    3) sélectionner la nouvelle Entity


    4) modifier son nom avec l'inspecteur de propriétés


     


     


  • DrakenDraken Membre
    juin 2017 modifié #4

    Sans grande originalité, j'ai appelé mon Entity Fiche.

  • Une Entity CoreData peut avoir des attributs (nom, âge et moyenne pour mes fiches).


    Pour définir un attribut, il faut sélectionner l'Entité, puis :


     


    1) cliquer sur le bouton (+) Add Attribute en bas de la fenêtre


    2) Définir le nom et le type de l'attribut


     


     


     


     


  • On ne peut pas mettre n'importe quoi dans une Entity. On ne peut utiliser que les types existants.


     


    Mon premier attribut est un nom de type String.


     


     


  • Au final, j'ai défini 3 attributs pour mon objet (Entity) Fiche :


     


    - un nom de type String


    - un age de type Int32


    - une moyenne de type Float


     


     


     


     


  • DrakenDraken Membre
    juin 2017 modifié #8

    La gestion de CoreData s'effectue par le biais d'un objet appelé PersistentContainer, généré automatiquement par Xcode si l'option Use Core Data est coché à  la création de l'application. Il est associé au délégué de l'Application. Il faut récupérer sa valeur à  chaque accès à  CoreData ou la stocker dans une variable.


     


    Moi j'ai préféré lire sa valeur avec une petite routine, défini dans le viewController.



    // Lecture du context CoreData
    func recuperationContextCoreData() -> NSManagedObjectContext? {
    return (NSApplication.shared().delegate as? AppDelegate)?.persistentContainer.viewContext
    }


    Il est de type NSManagedObjectContext?. Les NSManagedObject sont la clé pour utiliser CoreData sans (trop) de prise de tête. L'Entity Fiche que j'ai défini avec l'éditeur graphique de CoreData est traité automatiquement comme une classe de type NSManagedObject.


     


    Pour créer un objet de type Fiche, il faut l'initialiser avec le context CoreData :



    let fiche = Fiche(context: context)

    Et remplir les attribut avec les bonnes propriétés :



    fiche.nom = nomAleatoire
    fiche.age = ageAleatoire
    fiche.moyenne = moyenneAleatoire


    Attention, le contexte ne sauve pas automatiquement les données dans CoreData. Il faut lui demander de le faire.



    // Sauvegarde des modifications dans le persistentContainer
    do {
    try context.save()
    } catch {
    print("ERREUR écriture CoreData")
    }

    Ce code sauve dans le fichier CoreData (le persistentContainer) toutes les modifications des objets liées au contexte depuis la dernière sauvegarde.


     


    Voici tous les morceaux de code réunis dans une fonction :



    func creerNouvelleFiche() {

    guard let context = recuperationContextCoreData()
    else { return }

    // Création données aléatoires
    let posAleatoire = arc4random_uniform(UInt32(listeNoms.count))
    let nomAleatoire = listeNoms[Int(posAleatoire)]
    let ageAleatoire = Int32(10 + arc4random_uniform(4))
    let moyenneAleatoire:Float = 7 + Float(arc4random_uniform(8))/2.0

    // Création fiche
    let fiche = Fiche(context: context)
    fiche.nom = nomAleatoire
    fiche.age = ageAleatoire
    fiche.moyenne = moyenneAleatoire

    // Sauvegarde des modifications dans le persistentContainer
    do {
    try context.save()
    } catch {
    print("ERREUR écriture CoreData")
    }

    print ("********")
    print ("Création nouvelle fiche")
    }


    Le template d'application Apple défini le contexte comme étant de type NSManagedObject?. Cela veut dire que (parfois, éventuellement, peut-être) il peut se produire un problème avec le persistentContainer (impossibilité de le créer par manque de mémoire disque, par exemple). Dans ce cas, il sera de valeur nil.


     


    La plupart des tutos sur CoreData lise la valeur du contexte en le forçant avec l'opérateur "!". C'est MAL ! Et plantogéne. Bon d'accord, il est peu probable que le context soit nil, mais cela peut arriver sur un ordinateur/device n'ayant plus de mémoire disque. Et là , paf .. le plantage ! Et un mauvais commentaire sur le Store : "L'application iTruc plante sans cesse. c'est une honte de vendre une horreur pareille. BOUH !".


     


    C'est pourquoi je récupère la valeur du contexte CoreData avec un guard, au début de la fonction.


     




    guard let context = recuperationContextCoreData()
    else { return }


    Si le context est nil, la fonction ne s'exécute pas et ne PLANTE PAS !


     


    Pour une application commerciale, j'aurais ajouté l'affichage d'un message à  l'utilisateur. Quelque chose comme : "L'application ne parvient pas à  écrire des données sur le disque. Il est possible que votre disque soit complètement remplis".


     


    Un message d'alerte vaut toujours mille fois mieux qu'un PLANTAGE SEC !


     


    EDIT : Le nom des élèves est généré aléatoirement à  partir d'un tableau de String :



    let listeNoms = [
    "Jacques",
    "Pierre",
    "Robert",
    "Eric",
    "Pépin",
    "Anne",
    "Sophie",
    "Yuri",
    "Aruma",
    "Zork",
    "Laurent",
    "Marie"
    ]


  • Etape suivante, comme lire le contenu des fiches ?


     


    Nous avons déjà  parlé de la manière de lire des données CoreData dans ce topic :


     


    http://forum.cocoacafe.fr/topic/15239-introduction-à -coredata-swift-3/


     


    Je me contente juste de donner le code nécessaire au chargement des Fiches :



    func chargementFiches() -> [Fiche]? {

    guard let context = recuperationContextCoreData()
    else { return nil }

    let requete = Fiche.fetchRequest() as NSFetchRequest<Fiche>
    var liste:[Fiche]?

    do {
    liste = try context.fetch(requete)
    } catch {
    print ("ERREUR requete CoreData")
    }

    // Si l'instruction fetch(requete) ne trouve rien
    // elle retourne un tableau [Fiche]? de taille nulle
    // Je trouve que renvoyer un tableau optionnel avec un contenu vide
    // n'est pas vraiment dans l'esprit Swift.
    // J'ai donc ajouté un test pour retourner nil
    // dans ce cas.
    if let liste = liste {
    if liste.count == 0 {
    return nil
    }
    }

    return liste
    }


    La méthode chargementFiches() scanne le fichier CoreData, pour retourner un tableau contenant tous les objets de type Fiche.


     


    Il suffit ensuite de parcourir le tableau pour consulter le contenu des fiches :



    func affichageFiches() {

    print ("************")
    print ("AFFICHAGE DES FICHES")

    // Chargement à  partir de CoreData
    if let listeFiches = chargementFiches() {
    // Affichage des fiches
    for fiche in listeFiches {
    print (" ")
    // Les chaà®nes CoreData sont des String?
    // il faut les convertir pour l'affichage
    if let nom = fiche.nom {
    print ("Nom : ", nom)
    }
    print ("Age : ", fiche.age)
    print ("Moyenne : ", fiche.moyenne)
    }

    } else {
    print ("Aucune fiche enregistrée !")
    }
    }

  • Pour détruire un objet CoreData, il suffit de le demander au context.



    context.delete(fiche)


    Mais attention, la destruction n'est pas immédiate. Et ne sera effective qu'après la prochaine mise à  jour du contexte.



    // mise à  jour du contexte
    do {
    try context.save()
    } catch {
    print("ERREUR écriture CoreData")
    }

    Voici une routine détruisant TOUS les objets de type Fiche contenus dans CoreData :



    func destructionFiches() {
    guard let context = recuperationContextCoreData()
    else { return }

    print ("*******************")
    print ("DESTRUCTION DES FICHES")

    // Chargement des fiches
    if let listeFiches = chargementFiches() {
    for fiche in listeFiches {
    context.delete(fiche)
    }
    // mise à  jour du contexte
    do {
    try context.save()
    } catch {
    print("ERREUR écriture CoreData")
    }

    print ("Destruction terminée..")

    } else {
    print ("Aucune fiche. Destruction impossible")
    }

    }

    Elle commence par charger les Fiches, puis fait un test pour vérifier qu'il en existe au moins une. Un message d'erreur est affiché si aucune fiche n'existe.

  • J'ai écrit une petite application pour tester tout ça, mélangeant interface macOS et affichage console.


    A la première utilisation, elle affiche qu'il n'y a aucune fiche.


     



     


    Aucune fiche au démarrage



     


     


     


     


  • DrakenDraken Membre
    juin 2017 modifié #12

    Je presse sur le bouton "Affichage des fiches". Elle me répond que :


     



     


    ************


    AFFICHAGE DES FICHES


    Aucune fiche enregistrée !


     


     


    Puis, sur le bouton "Détruire toutes les fiches" :


     



     


    *******************


    DESTRUCTION DES FICHES


    Aucune fiche. Destruction impossible


     


     


    Et pourquoi pas "créer nouvelle fiche aléatoire" ?. Il s'affiche alors :


     



     


    ********


    Création nouvelle fiche


     


     


    Nouveau clic sur le bouton "Affichage des fiches" :


     



     


    ************


    AFFICHAGE DES FICHES



    Nom :  Yuri


    Age :  12


    Moyenne :  10.5



    Il y a bien un élève (aléatoire), avec une moyenne de 10,5

     

    Je presse deux fois de plus sur le bouton de création, et j'affiche la nouvelle liste :

     



     


    ************


    AFFICHAGE DES FICHES



    Nom :  Yuri


    Age :  12


    Moyenne :  10.5



    Nom :  Aruma


    Age :  11


    Moyenne :  10.5



    Nom :  Jacques


    Age :  11


    Moyenne :  7.5


     


     



    J'ai bien 3 élèves, dont le dernier est un cancre !
  • Je quitte l'application avant de la relancer. Il s'affiche :


     



     


    Nombre fiches au démarrage :  3



     


    Il y a bien eu pertinence des données, malgré l'arrêt du programme.


     


    Pour en être certain, je clique sur le bouton "Affichage des Fiches".


     



     


    ************


    AFFICHAGE DES FICHES



    Nom :  Yuri


    Age :  12


    Moyenne :  10.5



    Nom :  Aruma


    Age :  11


    Moyenne :  10.5



    Nom :  Jacques


    Age :  11


    Moyenne :  7.5


     


     


    Pas de doute, Jacques est vraiment un cancre !

  • DrakenDraken Membre
    juin 2017 modifié #14

    Petit arrêt pour cause de résultats électorales. 


     


    Je détruit les fiches avec le bouton "Détruire toutes les fiches" 


     



     


    *******************


    DESTRUCTION DES FICHES


    Destruction terminée..


     


     


    Nouvelle pression sur "Afficher les fiches" :


     



     


    ************


    AFFICHAGE DES FICHES


    Aucune fiche enregistrée !


     


     


    Je quitte l'application et je relance :


     



     


    Aucune fiche au démarrage


     


     


    Persistence .. ok !


     


    C'est a peu prés tout ce que je sais faire avec CoreData, du moins pour le moment.


     


     


    Le code source peut être téléchargé ici  :

  • Cool, je faire lire çà  attentivement mais un peu plus tard, maintenant c'est à  mon tour d'avoir un PB : avec un Congélateur qui refuse de se remettre en route et avec le temps que l'on a, c'est pas du gateau.


     


    Merci A +


  • PatyomPatyom Membre
    juin 2017 modifié #16

    Très bien, là , je comprends nettement avec facilité. Tu te doutes que j'ai encore des questions dessus !


     


    Peut-on visionner le fichier "Fiches", pour voir comment c'est stocké ?


     


    Dans ma future BDD j'ai des dates à  inclure et bien entendu quand je vais faire une requête avec mois ou année en recherche, dons il faut que j'ajoute des positions dans ma fiche avec par exemple : une pour le Mois, une autre pour l'Année ? c'est bien comme cela que ça fonctionne ?


     


    Oui je le demande car dans ma jeunesse dans ma BDD, chaque fiche avait une clé (ID) qui était créée par programme dans laquelle j'incluais les valeurs "AAMM", au fait, y-a-t'il une clé d'enregistrement ?


     


    en tout cas merci




  • Très bien, là , je comprends nettement avec facilité. Tu te doutes que j'ai encore des questions dessus !


     


    Peut-on visionner le fichier "Fiches", pour voir comment c'est stocké ?


     


    Dans ma future BDD j'ai des dates à  inclure et bien entendu quand je vais faire une requête avec mois ou année en recherche, dons il faut que j'ajoute des positions dans ma fiche avec par exemple : une pour le Mois, une autre pour l'Année ? c'est bien comme cela que ça fonctionne ?


     


    Oui je le demande car dans ma jeunesse dans ma BDD, chaque fiche avait une clé (ID) qui était créée par programme dans laquelle j'incluais les valeurs "AAMM", au fait, y-a-t'il une clé d'enregistrement ?


     




    Euh .. pas la moindre idée. Je ne connais pas grand chose aux bases de données, moi. Je découvre CoreData pratiquement en même temps que toi. Ma partie c'est plutôt le graphisme, les jeux vidéos, le game design, et l'écriture de tutos.

  • CéroceCéroce Membre, Modérateur

    Peut-on visionner le fichier "Fiches", pour voir comment c'est stocké ?

    Le stockage se fait (par défaut) dans une BdD SQLite, donc tu peux ouvrir le fichier avec un logiciel capable d'éditer les bases SQLite. Bon, après, quand tu verras la gueule de la base, tu vas vite comprendre que ce n'est pas fait pour ajouter des tables toi-même.

    De toute façon, ce n'est pas la philosophie de Core Data: c'est un ORM (Object-Relationship Mapping), qui sert à  traduire les Objets dans un stockage BdD relationnelle " ce qui est un problème en soi, si tu veux mon avis. Tu ne dois pas penser "BdD" mais "Objet".

    Dans ma future BDD j'ai des dates à  inclure et bien entendu quand je vais faire une requête avec mois ou année en recherche, dons il faut que j'ajoute des positions dans ma fiche avec par exemple : une pour le Mois, une autre pour l'Année ? c'est bien comme cela que ça fonctionne ?

    J'ai un doute, il n'y a pas une type primitif Date dans Core Data ? (Pas le temps de vérifier).
     

    au fait, y-a-t'il une clé d'enregistrement ?

    Oui, mais tu n'y a pas accès. 



  • J'ai un doute, il n'y a pas une type primitif Date dans Core Data ?

     




    Yes Sir !

  • Joanna CarterJoanna Carter Membre, Modérateur
    juin 2017 modifié #20

    Les dates se trient en CoreData. Il suffit de dire l'ordre dans un NSSortDescriptor lorsque tu demande les données.


     


    Ce n'est pas nécessaire de séparer les composants


  • Joanna CarterJoanna Carter Membre, Modérateur

    Petit exemple d'un modèle pour un de mes applis, qui gère un festival


     


  • Joanna CarterJoanna Carter Membre, Modérateur

    Et le code pour récupérer la liste des Events dans un NSFetchedResultsController :



    private var _fetchedResultsController: NSFetchedResultsController<CDEvent>?

    fileprivate var fetchedResultsController: NSFetchedResultsController<CDEvent>?
    {
    get
    {
    if _fetchedResultsController == nil
    {
    let request = CDEvent.fetchRequest() as NSFetchRequest<CDEvent>

    let daySort = NSSortDescriptor(key: "day.date", ascending: true)

    let startTimeSort = NSSortDescriptor(key: "startTime", ascending: true)

    let endTimeSort = NSSortDescriptor(key: "endTime", ascending: true)

    let nameSort = NSSortDescriptor(key: "artist.imageName", ascending: true)

    request.sortDescriptors = [daySort, startTimeSort, endTimeSort, nameSort]

    self._fetchedResultsController = NSFetchedResultsController(fetchRequest: request,
    managedObjectContext: DataProvider.shared.viewContext,
    sectionNameKeyPath: "day.narrative",
    cacheName: nil)
    }

    do
    {
    try _fetchedResultsController!.performFetch()
    }
    catch
    {
    fatalError("Failed to initialize fetchedResultsController: \(error)")
    }

    return _fetchedResultsController
    }
    }
  • Peut-on rajouter CoreData à  un projet ?

  • Joanna CarterJoanna Carter Membre, Modérateur
    juin 2017 modifié #24
    Mais bien sûr. Il ne faut qu'ajouter une classe comme ci http://forum.cocoacafe.fr/topic/15280-tutoriel-séparation-de-coredata-de-lappdelegate/?p=147749 et créer un nouveau fichier .xcdatamodeld avec le même nom que tu mets dans le code.
  • Une actualisation Swift 4, on dit que Core Data a bien évolué, un petit ou grand Tuto même en Anglais je suis preneur ?


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