Контекст управляемого объекта в iOS почему-то отсутствует - PullRequest
0 голосов
/ 14 мая 2018

Я использую Alamofire для отправки запроса к конечной точке, используя Swift.Я анализирую объекты JSON, которые я получаю из ответа, используя протокол Codable, а затем пытаюсь вставить объекты в базовые данные, используя подклассы управляемых объектов.Однако, когда я делаю это, я получаю сообщение об ошибке, в котором говорится, что мой родительский контекст управляемых объектов (MOC) равен нулю.Это не имеет смысла для меня, потому что я устанавливаю MOC через Dependency Injection из AppDelegate и подтверждаю, что оно имеет значение, распечатывая его значение на консоль в методе viewDidLoad ().

Вотмой соответствующий код:

Я установил здесь свой MOC:

class ViewController: UIViewController {

var managedObjectContext: NSManagedObjectContext! {
    didSet {
        print("moc set")
    }
}


override func viewDidLoad() {
    super.viewDidLoad()
    print(managedObjectContext)
}

///

func registerUser(userID: String, password: String) {

    let parameters: [String: Any] = ["email": userID, "password": password, "domain_id": 1]
    let headers: HTTPHeaders = ["Accept": "application/json"]

    Alamofire.request(registerURL, method: .patch, parameters: parameters, encoding: JSONEncoding.default, headers: headers).responseJSON { response in

        switch response.result {
        case .success:

            if let value = response.result.value {
                print(value)
                let jsonDecoder = JSONDecoder()

                do {
                    let jsonData = try jsonDecoder.decode(JSONData.self, from: response.data!)
                    print(jsonData.data.userName)
                    print(jsonData.data.identifier)
                    print(self.managedObjectContext)

                    let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
                    privateContext.parent = self.managedObjectContext

                    let user = UserLogin(context: privateContext)
                    user.userName = jsonData.data.userName
                    user.domainID = Int16(jsonData.data.identifier)
                    user.password = "blah"

                    do {
                        try privateContext.save()
                        try privateContext.parent?.save()
                    } catch let saveErr {
                        print("Failed to save user", saveErr)
                    }

                } catch let jsonDecodeErr{
                    print("Failed to decode", jsonDecodeErr)
                }

            }
        case .failure(let error):
            print(error)
        }
    }
}

Я получаю конкретное сообщение об ошибке:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Parent NSManagedObjectContext must not be nil.'

Я понимаю, что Alamofire загружает данные в фоновом потоке, поэтому я использую дочерний контекст, но я не уверен, почему родительский объект равен nil.

Вот настройкакод для моего контекста управляемого объекта:

class AppDelegate: UIResponder, UIApplicationDelegate {

var persistentContainer: NSPersistentContainer!
var window: UIWindow?


func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.

    createContainer { container in

        self.persistentContainer = container
        let storyboard = self.window?.rootViewController?.storyboard
        guard let vc = storyboard?.instantiateViewController(withIdentifier: "RootViewController") as? ViewController else { fatalError("Cannot instantiate root view controller")}
        vc.managedObjectContext = container.viewContext
        self.window?.rootViewController = vc

    }

    return true
}

func createContainer(completion: @escaping(NSPersistentContainer) -> ()) {

    let container = NSPersistentContainer(name: "Test")
    container.loadPersistentStores { _, error in

        guard error == nil else { fatalError("Failed to load store: \(error!)") }
        DispatchQueue.main.async { completion(container) }

    }
}

Кто-нибудь может увидеть, что я делаю неправильно?

1 Ответ

0 голосов
/ 14 мая 2018

Я не вижу ничего сразу "неправильного", поэтому давайте немного отладим.

  1. Установите точку останова в applicationDidFinish... сразу после охраны.
  2. Установите точку остановапри создании privateContext.

Который срабатывает первым?

Где находится функция registerUser?В контроллере представления?Я надеюсь, что нет:)

точка останова сразу после того, как мое охранное заявление срабатывает первым.И да, моя функция registerUser действительно находится внутри ViewController.

Помещение сетевого кода в контроллеры представления - это запах кода.Контроллеры вида имеют одну работу, управляют своими видами.Сбор данных принадлежит контроллеру постоянства;например, расширение вашего NSPersistentContainer и размещение там кода для сбора данных.

Однако здесь проблема не в этом, а только запах кода.

Следующий тест.

Передается ли ваш постоянный контейнер и / или viewContext в контроллер представления и сохраняется ли он?

Уничтожается ли контроллер представления до того, как сработает блок?

Чтобы проверить этоЯ бы поставил утверждение перед Alamofire.request и произвел бы сбой, если контекст nil:

NSAssert(self.managedObjectContext != nil, @"Main context is nil in the view controller");

Я бы также поставил эту же строку кода непосредственно перед:

privateContext.parent = self.managedObjectContext

Беги снова.Что происходит?

Я выполнил тест, как вы описали, и я получаю ошибку: Поток 1: Утверждение не выполнено: Основной контекст равен nil в контроллере представления

Какойутверждение рухнуло?(вероятно, следует немного изменить текст ...)

Если это первый текст, то ваш контроллер представления не получает viewContext.

Если это второй, то viewContext возвращается к nil до выполнения блока.

Измените свои предположения соответствующим образом.

обнаружил кое-что, что имеет отношение к этому: если я помещу кнопку для вызова функции registerUser () по усмотрению пользователя вместо вызова ее непосредственно изМетод viewDidLoad (), сбоев нет, код работает нормально, а MOC имеет значение

. Это подводит меня к теории, что ваш registerUser() вызывался до вашего viewDidLoad().Вы можете проверить это, установив точку останова в обоих и посмотреть, какая из них срабатывает первой.Если ваш registerUser() срабатывает первым, посмотрите на стек и посмотрите, как он вызывается.

Если он выстрелит после вашего viewDidLoad(), тогда установите точку останова для свойства context и посмотрите, чтовозвращает значение nil.

Итак, если я удалю эту строку, как мне установить свойство MOC на моем RootViewController с помощью внедрения зависимостей?

Строкапрямо перед тем, как здесь ключ.

let storyboard = self.window?.rootViewController?.storyboard

Здесь вы получаете ссылку на storyboard от rootViewController, который уже создан и связан с window вашего приложения.

Поэтому вы могли бы изменить логику на:

(self.window?.rootViewController as? ViewController).managedObjectContext = container.viewContext

Хотя я бы очистил это и применил немного логики nil:)

Проблема IСледует понимать, что MOC в RootViewController используется ДО того, как MOC возвращается из замыкания и устанавливается в AppDelegate.Что мне здесь делать?

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

Без миграции мы обычно говорим здесь мс, а не секунды.

НО ... Вы хотите, чтобы один и тот же код обрабатывал загрузку пользовательского интерфейса, будь то миллисекунды или секунды.Как вы решаете, что решать вам (дизайнерское решение).Одним из примеров является продолжение представления загрузки до тех пор, пока уровень персистентности не будет готов, а затем переход на другой уровень.

Если бы вы сделали это, вы могли бы слегка изменить представление загрузки, если происходит миграция, чтобы проинформировать пользователя о том, почемутак долго.

...