[Résolu] Créer une classe pour le model coreData
Bonjour, suite à ce post sur lequel je reviendrai plus tard, je suis amené, comme j'ai encore du mal (sur Instruments) à identifier les leaks et les problèmes de mémoire qui gonfle sans arrêt (retain...), je me consacre donc à faire en premier lieu, les corrections laissées en suspend auxquelles je pense.
Tout d'abord, revoir mon model/CoreData. J'ai au moins 250 champs, je dois donc scinder ma base en plusieurs bases.
Et, ce faisant, j'en profite au passage pour externaliser les codes re récup/save/delete de coreData.
J'ai donc créé une classe qui me sert à faire ces requêtes.
Je travaille pour l'instant sur la récupération des données.
Cela marche super bien, mais je tente d'en faire plus, et je n'y parviens pas.
Voilà ce qui fonctionne :
// MARK: - Recup CoreData
func confCoreDate() {
let fetchEntity: FetchEntity = FetchEntity()
let fetchResults: NSArray = fetchEntity.getFetchResults("Patients", predicateField: listeDesVariables.nomString)
let singlePatient: Patients = fetchResults.lastObject as! Patients
// 1- Début de la partie à intégrer dans la classe
if let e1L1Regard = singlePatient.e1L1Regard {
listeDesVariables.e1L1RegardString = singlePatient.e1L1Regard!
} else {
listeDesVariables.e1L1RegardString = ""
}
if let e1L1Sourire = singlePatient.e1L1Sourire {
listeDesVariables.e1L1SourireString = singlePatient.e1L1Sourire!
} else {
listeDesVariables.e1L1SourireString = ""
}
// 2- Fin de la partie à intégrer dans la classe
}
Et la classe en question :
import UIKit
import CoreData
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext!
class FetchEntity {
func getEntity(entityName: String) -> NSEntityDescription {
let entityDescription = NSEntityDescription.entityForName(entityName, inManagedObjectContext: context)
return entityDescription!
}
func getFetchResults(entityName: String, predicateField: String) -> NSArray {
let fetchRequest: NSFetchRequest = NSFetchRequest()
fetchRequest.entity = getEntity(entityName)
fetchRequest.returnsObjectsAsFaults = false
let predicate: NSPredicate = NSPredicate(format: "nom == '\(predicateField)'")
fetchRequest.predicate = predicate
let fetchResults: NSArray = context.executeFetchRequest(fetchRequest, error: nil)!
return fetchResults
}
}
Jusque là , cela fonctionne parfaitement.
J'imagine que certaines remarques vont poindre, et elles seront les bienvenues.
Je pense par exemple, que je devrais mettre ( func getEntity() ) en private
Bref, j'en viens à ma question.
En gros je ne sais pas comment intégrer ce qui se trouve entre "// 1"- et "// 2-" dans le premier code.
J'ai tout essayé et je galère.
J'ai fait dans la classe, un truc du genre :
func getLvd(model: String, lvd: String, fetchResults: NSArray) {
let singlePatient: Patients = fetchResults.lastObject as! Patients
if let singleModel = singlePatient."\(model)" {
listeDesVariables.lvd = singlePatient.singleModel!
} else {
listeDesVariables.lvd = ""
}
}
Je ne sais pas :
A- Comment transmettre un e1L1Regard qui est un des attributs dans mon modèle (@NSManaged var e1L1Regard: String?)
J'ai essayé de transmettre un NSManaged, un String,...
B- Comment cibler l'attribut comme dans (singlePatient.e1L1Regard!), mais faire cela dans la classe.
Voilà , j'espère que c'est assez clair.
Je suis sûr que c'est évident pour les barmen, mais moi, je suis encore en cuisine... 8--)
Merci d'avance.
Réponses
Je me répond à moi-même, et pour ceux qui cherchent à faire la même chose.
J'y suis parvenu. Je suis énormément gravement joyeusement heureux d'avoir réussi, car cela va me faire gagner au moins 1000 lignes de codes sur tout mon projet. Et j'espère que ça va alléger la mémoire consommée.
Voilà le code...
La classe :
Et les appels à la classe :
Merci pour votre aide en pensée...
Une petite question, au passage.
Est-il dangereux, voire pas recommandé d'avoir un fichier dans lequel je stocke toutes les variables de tous les écrans, modifiées à partir de chacun des écrans ? Cela me permet de lire n'importe quelle variable à partir de n'importe quel écran...
Voilà le type de fichier. Y'en a qui vont hurler...
Et je l'appelle sur mon container général ainsi :
Ainsi, j'attrape une variable comme ça (par exemple, bien sûr) à partir d'un écran :
Merci pour vos remarques.
L'intention n'est pas choquante mais la manière oui.
Puisque tu utilises CoreData tu pourrais utiliser le mapping entre entité CoreData et classe.
Tu ne devrais utiliser des variables var que pour les données pouvant changer après la création.
Exemple :
C'est mieux avec un let :
Bonjour,
Pour ta dernière question : Pour moi l'intention et la manière ne sont pas bonnes. Le code devient illisible, tu ne respectes pas plusieurs principe du développement objet ( encapsulation, responsabilité unique des classe,....) et oui c'est dangereux de faire ça : Tout le monde a accès à tout de n'importe ou.
Je vois aussi que tu accèdes au délégué de ton application a partir de la classe qui gère CoreData, je sais que ça vient des templates par défaut Apple... il vaut mieux mettre toute la logique CoreData ailleurs, par exemple dans la même classe qui gère les entités, c'est beaucoup mieux que d'avoir une dépendance au delégué de ton application.
Il me semble que tu as un paramètre en plus (fetchResults) dans ta méthode getField. Normalement il devrais y avoir une erreur/warning non ?
Mais ce qui est dommage avec cette approche c'est que :
- normalement tu as déjà toutes ces données dans CoreData, donc pourquoi les dupliquer dans une structure géante ?
- cette classe est encore une fois bien trop longue et peu structurée
Déjà , si tu sais qu'il y en a qui vont hurler, c'est que tu sais qu'il y a quelquechose qui ne va pas là -dedans, c'est déjà un bon début ;-)
Sinon, voilà ce que je te propose pour que, si jamais tu veux vraiment continuer à partir sur cette voie plutôt que d'utiliser tes données déjà dans CoreData, au moins rendre to code plus structuré et plus lisible :
De même, pour l'adoption de tes protocoles dans ton ContainerViewController, cette liste énorme de protocole me semble tout à fait justifiée, mais aussi un peu longue à mettre sur une ligne.
Avec la possibilité que donne Swift de faire conformer une classe à un protocole via une extension, je trouve ça bien plus propre et lisible de découper ta classe ContainerViewController en extensions, en créant une extension par protocole à se conformer contenant les méthodes dudit protocole. Ainsi :
Non seulement c'est beaucoup plus structuré avec une organisation hiérarchique et de la composition de petites structures plutôt qu'une géante, mais en plus ça te permet si besoin de ne passer que le struct nécessaire à tes différents écrans ou tes différentes fonction de traitement. Tu peux ainsi ne passer que l'élément de type Etape1.Level2 à une fonction si elle n'a besoin que de ça et n'a pas à s'embrasser de la totalité de ton objet géant ListeDesVariables qui fait quand même très fourre-tout et volumineux.
---
Après, tout ça ne répond pas vraiment à ta question "est-ce que c'est une bonne idée d'avoir une grosse ListeDeVariables partout" (à part sur le fait que "si tu le fais, il faut la passer de proche en proche et surtout pas en faire un singleton"), mais te permet déjà de mieux structurer ton code.
Et en tout cas c'est ce que je t'expliquais déjà dans des réponses précédentes, j'espère que c'est aussi sous ce genre de forme hiérarchique que tu as organisé tes entités/tables de ta base CoreData, à découper tes éléments en petites entités structurelles, comme j'ai fait pour Zoom et Poignee par exemple, plutôt que de tout garder à plat.
---
Pour moi la bonne solution ce n'est pas d'avoir cette ListeDesVariables, puisque tu as déjà toutes ces infos dans CoreData à priori, mais juste d'utiliser les données de CoreData pour stocker ça, pas la peine d'avoir ça en double. Mais du coup, de bien organiser les entités CoreData dans la même idée que ce que je viens de te montrer, de façon hiérarchique en découpant des objets en petits objets. Par exemple, c'est une aberration que d'avoir ces 6 attributs pour ta poignée qui sont à plat mélangé avec le reste, alors qu'on sent bien que ces 6 attributs sont intimement liés et qu'ils forment à eux 6 la définition d'une poignée. Ou à minima, si tu les groupes pas tous les 6, fais toi au moins une structure/entité CoreData qui groupe le X et le Y ensemble !
@Aligator : Tu utilises des structures pour tes objets modèle ?
Mais bon après ça se discute selon les cas. Je ne connais pas assez le contexte de l'application de Théo pour savoir si c'est vraiment justifié. En pratique c'était pour justement faire tilter Théo sur le fait qu'en Swift on pense aussi protocoles et struct avant de partir tête baissée sur une class comme on le ferait en ObjC.
C'est d'ailleurs un sujet abordé dans de mon article de blog de cette semaine que je compte poster aujourd'hui ou demain, même si je compte en fait pointer sur l'excellent talk de Andy Matuschak sur le sujet.
En effet, autant pour les données constantes ça ne pose pas de problème : Autant si on garde des struct, quand on va vouloir commencer à changer les valeurs alors qu'on les aura passé à une autre variable, ça va changer la copie mais pas l'original : Alors que si on passe les Etapes et Levels en class et non en struct, dans ce cas tout est passé par référence et on peut faire :
Merci pour tous vos efforts à me faire entrer dans le droit chemin
- jpimbert
J'ai cherché partout, et je ne comprend pas, c'est quoi ce fameux mapping ?
- Draken
Tu as carrément raison, un oubli, c'est corrigé. Merci.
- Samir
Tu parles de ça ?
Dans la classe (class FetchEntity) ?
Ben justement, ma classe (class FetchEntity), je croyais qu'elle gérait les entités. Je l'ai créée pour cela.
Je n'ai donc encore rien compris. Pourtant, j'en lis des trucs sur coreData !
Non, non, j'ai bien vérifié. Merci pour la correction.
- AliGator
Justement, je n'ai pas encore pris le temps de m'en occuper, mais t'inquiète, c'est l'objectif number One.
C'est pour cela, que je suis en train d'essayer d'optimiser tout cela.
Je t'ai lu au complet, et je dois te relire et essayer de tout bien comprendre pour le meilleur choix.
Merci pour ce cours énorme. J'adore.
AliGator,
une petite question.
Justement, en ayant fait cette classe (FetchEntity), et en m'en servant ainsi,
Je me disais justement que :
1- C'était con de stocker les données coreData dans des variables dans une classe externe (ListeDesVariables)
et 2- Que c'était con de stocker tout court ces données, dans des variables.
Donc, ma question est :
Si je ne stocke pas les données coreData dans des variables (externes, locales, array, dictionnary, etc), cela signifie qu'à chaque fois que j'applique des traitements sur ces données, je dois le faire directement sur ces données ?
Par exemple (en variables locales - sans listeDesVariables):
Plutôt que de faire :
Il vaut mieux faire cela ?
Et par exemple, plus loin dans un
plutôt que
je fais cela ? (setField évidemment plutôt que getField)
Et tout les traitements que je dois faire partout, on doit faire comme cela ?
J'ai souvent des traitements de motifs de coordonnées (x et y) en temps réel (mes fameuses poignées), je fais ça aussi en direct sur coreData ? Huuummmm !
On voit les poignées ici à environ 48 secondes.
En gros, moi je pensais que l'on devait éviter de taper sans arrêt dans la base, mais taper une seule fois, puis stocker ce dont on a besoin localement dans un array ou des var...
CoreData plus rapide que des vars ?
CoreData moins gourmand en mémoire ?
etc.
Merci d'avance
AliGator
J'adore ton code, y' plein de trucs qui m'inspirent (struct, protocole, organisation, ...).
D'ailleurs, "protocol", j'hallucine... J'en ai encore beaucoup à apprendre.
Merci pour le temps que tu as du passer pour le faire, et donc t'imprégner d'un projet qui n'est pas le tien.
En regard de ta remarque et de ma réponse (juste au-dessus)
effectivement, si c'est le cas, je laisse tomber cette ListeDesVariables, et voire, plus aucune variables locales inutiles...
Pour les
C'est génial.
Je vais essayer...
Perso, à mes débuts j'ai essayé plusieurs fois CoreData, mais ça me faisait peur et j'y comprenais pas grand chose, bien trop de classes et de concepts nouveaux à apprendre et à comprendre. J'ai plusieurs fois essayé de m'y mettre et ai abandonné de découragement. Et puis un jour j'ai découvert MagicalRecord. Et là tout est allé comme dans du beurre.
j'en ai même fait un talk lors d'une conférence CocoaHeads Rennes que je t'invite fortement à regarder, pour moi ça a été une révélation.
Par exemple je suis un peu perdu par ton code
Moi avec MagicalRecord pour récupérer le Patient dont le nom est nomString et modifier son attribut e1L3Forcer je ferais:
Bon ça fait des plombes que j'ai pas touché à CoreData et donc MagicalRecord et je connais pas son API Swift, mais l'idée est là .
De ce que j'ai compris tu as toi-même commencé un espèce de classe "wrapper" autour de CoreData pour te faciliter la vie, mais c'est dommage de réinventer la roue (et certainement pas aussi bien) alors que MagicalRecord te met déjà à disposition qui encapsule tout ça pour te fournir une API tellement plus lisible...
Ok AliGator.
Je suis en train de regarder ta vidéo. Mais c'est en objC. Je peux m'en servir sur un projet en swift ?
Et sans quoi, pour une de mes nombreuses questions.
1- Il faut travailler en direct sur la BDD (motifs de coordonnées, etc.), et donc enregistrer en temps réel
2- Ou bien prendre dans la BDD, puis stocker dans des variables, et travailler sur nos variables, puis réenregistrer dans la BDD ?
Merci.
Merci beaucoup.
Bon j'ai bien regardé la vidéo, et j'ai téléchargé le framework et j'ai tous les liens des tutos et d'install.
Je ne me sens pas le courage pour l'instant de l'installer sur ce projet, car j'ai tellement à faire, à restructurer.
Je testerais cela sur un autre projet. Mais ça m'intéresse grave. Encore merci d'avoir insister.
Objet de ma question.
Dans un premier temps, j'ai viré tous mes accès dans tous mes fichiers à des variables dans le fichier externe bidon (ListeDesVariables)
Dans un second temps, puisque je ne charge plus toutes les variables à partir de ce fichier, au départ d'un click tableView, je ne récupère que celles dont j'ai besoin dans chacun de fichiers. C'était fastidieux, mais bon, ça marche.
Et j'ai ma p'tite classe (le wrapper coreData que je devrais améliorer, j'en conviens).
Mais je pense, que, déjà , avec ces améliorations, je devrais gagner en mémoire (sur Instruments). Je testerai cela plus tard, j'ai encore des motifs de restructuration à faire sur des images, etc.
Mais comme maintenant mon fichier externe bidon des variables, est très épuré, je me demandais si je ne pouvais pas m'en débarrasser complètement.
Je m'explique, j'ai quelques variables (et quelques constantes) à l'intérieur, dont j'ai systématiquement besoin partout dans le projet.
Et je me demandais (encore une fois :-* ), s'il n'était pas préférable de les mettre en variables globales (tes cheveux se hissent sur la tête, hein) dans mon container général. Ce serait cool.
Voici la liste des éléments en question :
Il s'agit des noms des écrans, des noms des controllers, des controllers, du flag (je click dans le tableview ou je crée un nouveau patient), de l'objet nouvellement créé.
Voici d'autre part, les quelques globales que j'ai déjà dans mon container général :
Evidemment (var listeDesVariables = ListeDesVariables()), ça va dégager si t'es ok pour les globales...
C'était ma question...
Merci d'avance
Désolé, une autre petite question, ou plutôt remarque, concernant les extensions par protocole.
J'en ai testé un, ça marche nickel. Cool.
Mais j'ai des méthodes qui sont dans plusieurs protocoles (Les accès aux étapes - précédentes et suivantes - Et aussi, la configuration de ma navBar). Je dois donc les placer dans plusieurs extensions. Pas de problème ?
Bon, en attendant ta réponse, je le fais quand même...
En même temps, en le faisant, je me rend compte que ça fait quand même beaucoup de lignes de code en plus, juste pour éviter la longue ligne de départ (qui moi, ne me dérange pas, si ça ne pose pas de problème à la mémoire ou au proc).
Ca c'est pas nouveau c'était déjà une règle d'or en Objective-C
Les "let" globales c'est ok vu que ce sont des constantes et dont qu'il n'y a pas les risques de multithreading et d'accès concurrents pouvant faire crasher ton app comme avec les "var". Là encore, rien de nouveau c'est la même règle qu'en ObjC
Concentrant les méthodes qui sont dans plusieurs protocoles si c'est le cas c'est qu'il y a un problème de conception là !! Comment ça se fait que tu as des méthodes avec exactement les mêmes noms dans des protocoles différents plutôt que d'avoir extrait ces méthodes communes dans un protocole dédié commun à tous ?!
Pour être franc des fois ta logique bizarre me dépasse...
Bonjour AliGator,
j'étais en train de te répondre, en m'expliquant sur mes pbs de var globales et
mais ça devenait long et compliqué à expliquer. J'ai tout effacé. Je recommence plus simple (enfin j'espère).
Méthodes/Protocoles :
En tout cas, j'ai des méthodes dans mon ContainerViewController, qui doivent être appelées par chacun des controllers.
Il s'agit de la conf de la nav custom et donc de ses boutons (nouveau patient, préférences, ouverture de la nav tableView de la gauche)
En gros, chacun des controllers à besoin d'exécuter la même méthode qui se trouve sur le ContainerViewController.
J'ai aussi des méthodes (goEtape1(levelAccesDirect: Int)) jusqu'à 6.
Et même remarque. Je dois pouvoir faire go5 à partir de plusieurs controllers.
Qu'en dis-tu ?
Variables globales :
Pour les var globales, je les met où, celles dont j'ai toujours besoin ?
Encore merci à toi.
Tu parles de méthodes qui sont déclarées plusieurs fois, ou juste appelées à plusieurs endroits ?
1) Si ce sont des méthodes identiques qui sont déclarées dans plusieurs de tes protocoles, comme ton message de la page précédente le laissait entendre, c'est qu'il y a un problème :
Là tu laisses penser que les méthodes sont déclarées dans plusieurs protocoles, et donc que tu dois les redéfinir avec leur implémentation dans plusieurs extensions à chaque fois. Et si c'est ça c'est qu'il y a un souci.
2) Mais si ce sont des méthodes déclarées uniquement dans ton ContainerViewController (comme le laisse entendre ton message précédent), et appelées ensuite de plusieurs endroits, alors je ne vois pas trop le souci ?! Ca ne change rien qu'elles soient déclarées dans le "class ContainerViewController : UIViewController" (si ces méthodes ne viennent pas d'un protocole) ou dans une des "extension ContainerViewController : UnProtocole" (si tu dois implémenter ces méthodes parce qu'elle est définie dans le protocole UnProtocole), dans tous les cas de l'extérieur tu les appelleras tout pareil "monContainerViewController.maMethode()", donc quel est le problème ?!
Qu'entends-tu par "je dois pouvoir faire go5" ? Tu entends bien "je dois pouvoir appeler la méthode go5 depuis n'importe quel controller ? Et alors, quel est le problème ? Le fait que cette méthode go5 soit déclarées dans la classe principale ou dans une extension (l'équivalent des catégories en Objective-C) ne change rien, du moment qu'elle est définie.
Si tu dois avoir des variables globales, c'est que ton code est mal fichu. La question n'est pas "je les met où", mais plutôt "comment je m'en débarrasse". Parmi les réponses, il y a "en fait c'est une constante et pas une variable, donc je dois utiliser let et pas var", ou alors "en fait je vais aller chercher les données dans CoreData et pas utiliser une variable globale pour ça", ou encore "je dois passer la variable de proche en proche" (ou pour la version expert : j'utilise de l'injection de dépendances)
Ben en fait, c'est exactement cela.
Je ne sais pas pourquoi, je me suis barré à l'époque dans les delegate/protocol. Certainement des erreurs de débutants qui apprennent un truc génial qu'ils ne comprennent pas ; ce qui est mon cas.
En plus, je l'utilise souvent ce principe controller.methode : j'adore ce truc, c'est un des trucs que je préfère.
Chui vraiment trop con.
Bref, j'ai viré tous mes delegate/protocol, sauf les deux principaux.
Je n'ai pu en mettre qu'un en extension.
Voilà la classe de mon container
Et ma seule extension
L'autre méthode que j'aurais aimé mettre en extension est (configureNavbar).
Mais je ne peux pas, car elle est appelée à partir des 6 controllers principaux, selon le principe controller.methode.
Et elle doit rester en delegate, car pour l'étape 0, cela semble nécessaire (à cause de la slideNav qui devient noire - Bref, laisse tomber, trop compliqué).
Pas grave, ça marche du feu de dieu, et ça commence à être très correct.
Je pense qu'on peut toujours, quand on est super pro, faire mieux, mais pour l'instant, je pense et surtout j'espère que c'est pas mal.
J'ai à -peu-près fait tout ce que tu me recommandais.
Je suis en train de virer toutes les variables globales, et aussi je reconfigure ma BDD (j'en suis à la moitié, c'est hyper long - pfff)
J'ai bon espoir. Je pense que demain, je vais pouvoir re-tester tout cela sur Instruments, avec moins de merdes qui trainent...
Vraiment, merci à toi oh Grand AliGator, d'avoir pris tout ce temps pour m'expliquer toutes ces choses dont une partie n'est pas encore assimilée ni appliquée, mais ça rentre tout doucement.
Au fait, ça
j'adore. Y'a pas plus clair.
Cela devient un de mes commandements à suivre.
Allez, je met résolu, ça le vaut bien, non !
Encore merci aux barmen.