Определение контекста для базовых данных с помощью AppDelegate как одиночного - PullRequest
1 голос
/ 23 марта 2019

Я пытаюсь разобраться в NSManagedObjectContext для базовых данных.Xcode 10.1 предоставляет довольно много стандартного шаблона, если флажок Core Data установлен при создании нового проекта.Но я нахожу это немного запутанным в отношении того, как задан текущий контекст для каждого контроллера представления.Я думаю, что у меня есть лучший способ, и я ищу совет, чтобы подтвердить или вернуть меня на правильный путь.

Например, в шаблонном коде AppDelegate, didFinishLaunchingWithOptions предоставляет контекст для MasterViewController следующим образом:

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

В MasterViewContoller первое использование контекста выбирает его из fetchedResultsController ANDесть код для сохранения контекста, хотя в AppDelegate уже есть функция saveContext (), позволяющая сделать то же самое:

@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)")
    }
}

В моем приложении с несколькими контроллерами представления я допустил ошибки, пытаясь-объявить или передать контекст в каждом месте, где это необходимо, поэтому пришлось столкнуться с ошибками, которые были вызваны непреднамеренным использованием более чем одного контекста.

Итак, мой вопрос заключается в следующем: я делаю ошибку или есть какой-то недостаток в следующем подходе:

1) Сделайте AppDelegate одиночным:

class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {

var window: UIWindow?

static let shared = AppDelegate()
…

2) В каждом классе, где это необходимо, всегда определяйте контекст (я предполагаю, что мне нужен только один) следующим образом:

let context = AppDelegate.shared.persistentContainer.viewContext

3) Всякий раз, когда необходимо сохранить контекст, делайте это так:

AppDelegate.shared.saveContext()

Это кажется намного проще, понятнее и менее подвержено ошибкам, и, похоже, работает в моей реализации.Есть ли проблемы с этим, которых я не вижу?

1 Ответ

0 голосов
/ 23 марта 2019

Честно говоря, примеры / шаблоны Apple всегда были плохим примером для начинающих, потому что они показывают только одну вещь и «взламывают» на отдыхе (например, принудительное развертывание всего). И начинающие, как правило, просто копируют этот подход.

Отказ от ответственности: я говорю о приложениях среднего размера. Вы всегда можете нарушать эти правила и рекомендации в небольших приложениях, потому что их использование может быть проще и приведет к упрощению приложения.

Сделать приложение легальным для одного:

В 99% не следует создавать экземпляр AppDelegate самостоятельно. Он обрабатывается для вас аннотацией UIApplication / @UIApplicationMain.

AppDelegate уже синглтон, поскольку каждое приложение имеет ровно один делегат на весь срок службы. Вы можете получить к нему доступ UIApplication.shared.delegate as? AppDelegate.

Но вы не должны. AppDelegate играть определенную роль в каждом приложении, предоставляя точку входа для связи между системой и вашим кодом, и вы не должны добавлять в него дополнительные роли (как дескриптор базы данных). , Доступ к нему где-то в кодовой базе в большинстве случаев является признаком запаха кода и плохой архитектуры.

Разделение стека CoreData

Доступ к базе данных является одним из немногих примеров хорошего использования шаблона Singleton. Но вместо использования AppDelegate вы должны создать отдельный сервис, который будет отвечать только за обработку связи с coredata (например, создание и обработка стека, отправка запросов и т. Д.).

Итак, CoreDataService - это путь.

Доступ к основным данным

Использование синглетонов не означает, что вы можете просто получить к нему доступ в любом месте, набрав Singleton.shared. Это значительно снизит тестируемость ваших компонентов и сделает их тесно связанными с синглетонами.

Вместо этого вы должны прочитать о Принципе внедрения зависимостей и ввести свои синглтоны. Например:

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

В идеале вы должны пойти еще дальше к SOLID и предоставить контроллеру только то, что ему действительно нужно:

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)

Несколько контекстов

Наличие нескольких NSManagedObjectContext может быть полезным и повысить производительность, но только если вы знаете, как с ними работать.
Это более продвинутая тема, поэтому вы можете игнорировать ее сейчас.
Вы можете прочитать об этом в Руководство по программированию основных данных

...