由于某种原因,iOS中的托管对象上下文为零

时间:2018-05-14 01:22:29

标签: ios swift core-data alamofire

我正在使用Alamofire使用Swift向端点提交请求。我使用Codable协议解析从响应中收到的JSON对象,然后尝试使用Managed Object Subclasses将对象插入到Core Data中。但是,当我这样做时,我一直收到错误,说我的父管理对象上下文(MOC)是零。这对我来说没有意义,因为我通过AppDelegate中的依赖注入设置了MOC,并通过在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 :(得分:1)

我没有立即看到任何东西"错误"所以让我们调试一下。

  1. 在警卫后面applicationDidFinish...放置一个断点。
  2. 在创建privateContext
  3. 时设置断点

    哪个先发射?

    registerUser功能在哪里?在视图控制器中?我希望不是:)

      

    在我的警卫声明首先发射之后的断点。是的,我的registerUser函数确实在ViewController中。

    将网络代码放在视图控制器中是一种代码味道。 View Controllers有一个工作,管理他们的视图。数据收集属于持久性控制器;例如,扩展您的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(),则在上下文属性上放置一个断点,然后查看将其设置回nil的内容。

      

    因此,如果删除该行,如何通过依赖注入在RootViewController上设置MOC属性?

    此前的线是这里的线索。

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

    在这里,您将获得storyboardrootViewController的引用,该window已经过实例化并与您应用的(self.window?.rootViewController as? ViewController).managedObjectContext = container.viewContext 相关联。

    因此您可以将逻辑更改为:

    nil

    虽然我会清理它并在其周围添加一些RootViewController逻辑:)

      

    我意识到的问题是AppDelegate中的MOC在从闭包返回MOC之前被使用,并在{{1}}中设置。我该怎么办?

    这是一个常见的同步(UI)与异步(持久性)问题。理想情况下,您的UI应该等到持久性加载。如果您加载持久存储,然后在存储加载后完成UI,它将解决此问题。

    如果没有迁移,我们通常会在这里谈论ms而不是秒。

    但是......无论是毫秒还是秒,您都需要相同的代码来处理UI加载。你如何解决这个问题取决于你(设计决定)。一个示例是继续加载视图,直到持久层准备就绪,然后转换。

    如果您这样做,那么如果发生迁移,您可以巧妙地更改加载视图,以告知用户为什么事情花了这么长时间。