4 votes

Définition du contexte pour les données de base avec AppDelegate comme singleton

J'essaie de me faire une idée de NSManagedObjectContext pour Core Data. Xcode 10.1 fournit une bonne quantité de texte passe-partout si la case Core Data est cochée lors de la création d'un nouveau projet. Mais je trouve que c'est un peu confus en ce qui concerne la façon dont le contexte actuel est défini pour chaque contrôleur de vue. Je pense avoir trouvé une meilleure solution et je cherche des conseils pour confirmer ou me remettre sur la bonne voie.

Par exemple, dans le code passe-partout de l'AppDelegate, didFinishLaunchingWithOptions fournit le contexte au MasterViewController comme ceci :

let masterNavigationController = splitViewController.viewControllers[0] as! UINavigationController
let controller = masterNavigationController.topViewController as! MasterViewController
controller.managedObjectContext = self.persistentContainer.viewContex

Dans le MasterViewContoller, la première utilisation du contexte le récupère du fetchedResultsController ET il y a un code pour sauvegarder le contexte fourni, même si l'AppDelegate a déjà une fonction saveContext() disponible pour faire la même chose :

@objc
func insertNewObject(_ sender: Any) {
    let context = self.fetchedResultsController.managedObjectContext
    let newEvent = Event(context: context)

    // If appropriate, configure the new managed object.
    newEvent.timestamp = Date()

    // Save the context.
    do {
        try context.save()
    } catch {
        // Replace this implementation with code to handle the error appropriately.
        // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        let nserror = error as NSError
        fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
    }
}

Dans mon application avec plusieurs contrôleurs de vue, j'ai fait des erreurs en essayant de redéclarer ou de transférer le contexte dans chacun d'entre eux lorsqu'il était nécessaire, et j'ai dû faire face à des erreurs causées par le fait d'avoir par inadvertance plus d'un contexte en circulation.

Donc ma question est la suivante : Est-ce que je fais une erreur, ou y a-t-il un inconvénient à l'approche suivante :

1) Faites de l'AppDelegate un singleton :

class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {

var window: UIWindow?

static let shared = AppDelegate()
…

2) Dans chaque classe où il est nécessaire, définissez toujours le contexte (je suppose que je n'en ai besoin que d'un seul) comme ceci :

let context = AppDelegate.shared.persistentContainer.viewContext

3) Chaque fois que le contexte doit être sauvegardé, faites-le comme ceci :

AppDelegate.shared.saveContext()

Cela semble beaucoup plus simple, plus clair et moins sujet à des erreurs, et semble fonctionner dans ma mise en œuvre. Y a-t-il des problèmes que je ne vois pas ?

4voto

ManWithBear Points 63

Pour être honnête, les exemples / modèles Apple ont toujours été de mauvais exemples pour les débutants, car ils ne montrent qu'une seule chose et "bidouillent" le reste (en forçant le déballage de tout par exemple). Et les débutants ont tendance à copier cette approche.

Avertissement : Je parle d'applications de taille moyenne à grande. Vous pouvez toujours enfreindre ces règles et recommandations dans les petites applications, car ne pas les utiliser peut être plus facile et conduire à une application plus simple.

Faites de l'AppDelegate un singleton :

Dans 99% des cas, vous ne devez pas instancier AppDelegate par vous-même. Il est géré pour vous par UIApplication / @UIApplicationMain annotation.

AppDelegate déjà singleton, puisque chaque application a exactement un délégué pendant toute sa durée de vie. Vous pouvez y accéder par UIApplication.shared.delegate as? AppDelegate .

Mais tu ne devrais pas. AppDelegate joue un rôle spécifique dans chaque application en fournissant un point d'entrée pour la communication entre le système et votre code et vous ne devriez pas lui ajouter des rôles supplémentaires (comme la gestion de la base de données). Le fait d'y accéder quelque part dans la base de données est dans la plupart des cas un signe de mauvaise architecture et d'odeur de code.

Séparation de la pile de données de base

L'accès aux bases de données est l'un des rares exemples de bonne utilisation du modèle Singleton. Mais au lieu d'utiliser AppDelegate vous devez créer un service distinct qui sera uniquement chargé de gérer la communication avec les corédonnées (comme la création et la gestion des piles, l'envoi de requêtes, etc.)

Alors CoreDataService est la voie à suivre.

Accès aux données de base

Utiliser des singletons ne signifie pas que vous pouvez y accéder n'importe où en tapant Singleton.shared . Cela réduira fortement la testabilité de vos composants et les rendra fortement couplés à des singletons.

Vous devriez plutôt vous renseigner sur Principe d'injection de dépendances et injectez vos singletons. Par exemple :

class MyViewController: UIViewController {
    let dataBaseManager: CoreDataService
    init(with dataBaseManager: CoreDataService) {
        self.dataBaseManager = dataBaseManager
        super.init(nibName: nil, bundle: nil)
    }
}

Idéalement, vous devriez même aller plus loin pour SOLIDE et ne fournir au contrôleur que ce dont il a réellement besoin :

protocol EventsProvider {
    func getEvents(with callback: [Event] -> Void)
}

extension CoreDataService: EventsProvider {
    func getEvents(with callback: [Event] -> Void) { 
        // your core data query here
    }
}

class MyViewController: UIViewController {
    let eventsProvider: EventsProvider
    init(with eventsProvider: EventsProvider) {
        self.eventsProvider = eventsProvider
        super.init(nibName: nil, bundle: nil)
    }
}

let vc = MyViewController(with: CoreDataService.shared)

Contextes multiples

Avoir plusieurs NSManagedObjectContext peuvent être pratiques et améliorer les performances, mais seulement si vous savez comment les utiliser.
C'est un sujet plus avancé, donc vous pouvez l'ignorer pour le moment.
Vous pouvez lire à ce sujet dans Guide de programmation des données de base

0voto

malcolmhall Points 1909

Une expérience intéressante consiste à utiliser la chaîne de réponse, à déplacer la méthode insertNewObject vers le délégué de l'application et à la modifier pour qu'elle appelle [self saveContext]. Ensuite, le bouton d'ajout envoie l'action à nil au lieu de self. Ou bien, dans le storyboard, faites glisser un élément du bouton de la barre et faites glisser son action jusqu'à la première icône du répondeur (elle se trouve en haut du contrôleur de vue et dans la barre latérale gauche), puis sélectionnez insertNewObject et essayez-le !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X