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

Posted

技术标签:

【中文标题】由于某种原因,托管对象上下文在 iOS 中为零【英文标题】:Managed Object Context is nil for some reason in iOS 【发布时间】:2018-05-14 01:22:29 【问题描述】:

我正在使用 Alamofire 向使用 Swift 的端点提交请求。我使用 Codable 协议解析从响应中收到的 JSON 对象,然后尝试使用托管对象子类将对象插入核心数据。但是,当我这样做时,我不断收到一条错误消息,说我的父托管对象上下文 (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) 

    

谁能看出我做错了什么?

【问题讨论】:

你能发布你的设置父 NSManagedObjectContext 代码吗? @QuocNguyen 完成。 我在这里看到了一些建议。你查了吗***.com/questions/13646696/… 不幸的是,这个建议对我的情况没有帮助。 【参考方案1】:

我没有立即看到任何“错误”,所以让我们稍微调试一下。

    applicationDidFinish... 中放置一个断点,就在守卫之后。 在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:断言失败:视图控制器中的主上下文为零

哪个断言崩溃了? (可能应该稍微改变一下文字......)

如果它是第一个,那么您的视图控制器没有收到viewContext

如果是第二个,那么viewContext 将在块执行之前返回到nil

相应地改变你的假设。

在这里发现了一些相关的东西:如果我根据用户的判断放置一个按钮来调用 registerUser() 函数,而不是直接从 viewDidLoad() 方法调用它,则不会发生崩溃,代码运行良好,并且MOC是有价值的

这使我认为您的registerUser() 在您的viewDidLoad() 之前被调用。您可以通过在两者中设置一个断点来测试它,然后查看哪个先触发。如果您的 registerUser() 先触发,请查看堆栈并查看调用它的内容。

如果它在您的viewDidLoad() 之后触发,则在上下文属性上放置一个断点,看看是什么将它设置回nil

那么,如果我删除该行,如何通过依赖注入在我的 RootViewController 上设置 MOC 属性?

前面的那一行是这里的线索。

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

在这里,您从rootViewController 中获得了对storyboard 的引用,该引用已经实例化并与您的应用程序的window 相关联。

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

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

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

我意识到的问题是 RootViewController 中的 MOC 是在 MOC 从闭包返回之前使用的,并在 AppDelegate 中设置。我在这里做什么?

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

在没有迁移的情况下,我们通常在这里谈论毫秒而不是秒。

但是...无论是毫秒还是秒,您都希望使用相同的代码来处理 UI 加载。你如何解决这个问题取决于你(设计决定)。一个例子是继续加载视图,直到持久层准备好,然后过渡。

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

【讨论】:

非常感谢您的及时回复!我按照你的要求做了,我的警卫声明之后的断点首先触发。是的,我的 registerUser 函数确实在 ViewController 中。 我不太熟悉进行单元测试(我需要在我的作品集中构建的众多领域之一),但我确实相信您刚刚提出的问题实际上是问题所在。我还有其他方法可以检查吗? 我按照你的描述运行了测试,我得到了错误:线程 1:断言失败:视图控制器中的主上下文为零 您正在代码中创建根 vc 的实例,但系统是否也自动为您实例化?如果您正在处理根的实例化,请确保您的项目设置为不使用情节提要。否则,您将创建两个 - 第一个由情节提要创建,将有一个 nil moc。您正在创建的第二个正在替换第一个并具有 moc。但到那时,损害已经造成。 @JasonC.Howlin 的出色表现!由于您创建了两次 UI,因此您需要删除其中一个。停止使用情节提要或停止手动实例化 UI。我怀疑删除 guard let vc = storyboard?.instantiateViewController(withIdentifier: "RootViewController") as? ViewController else fatalError("Cannot instantiate root view controller") 将是一个好的开始。

以上是关于由于某种原因,托管对象上下文在 iOS 中为零的主要内容,如果未能解决你的问题,请参考以下文章

CMFCPopupMenu - 右键单击​​上下文菜单快捷键由于某种原因消失

在托管对象上下文中添加 NSSortDescriptor 以获取请求?

如何在 iOS 应用程序中为 iFrame 设置透明背景?

如何将托管对象上下文分配给 iOS 中的应用程序委托?

保存托管对象上下文会在 iOS 5 的 performBlock 中创建死锁

iOS CoreData+MoGenerator:如何仅在使用嵌套上下文时初始化托管对象一次?