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

问题描述 投票:0回答:1

我正在使用 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 是在后台线程上下载数据,这就是我使用子上下文的原因,但我不确定为什么父上下文为零。

这是我的托管对象上下文的设置代码:

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

我做错了什么?

ios swift core-data alamofire
1个回答
1
投票

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

  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:断言失败:视图控制器中的主上下文为零

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

如果它是第一个,那么您的视图控制器不会收到

viewContext

如果是第二个,则

viewContext
将返回到块执行之前的
nil

相应地改变你的假设。

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

这让我得出这样的理论:你的

registerUser()
在你的
viewDidLoad()
之前被调用。您可以通过在两者中放置一个断点来测试它,看看哪一个先触发。如果您的
registerUser()
首先触发,请查看堆栈并查看是什么在调用它。

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

nil

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

它前面的那条线就是这里的线索。

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

在这里,您将从已实例化并与应用程序的 
storyboard

关联的

rootViewController
获取对
window
的引用。

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

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

尽管我会清理它并在其周围添加一些
nil

逻辑:)


我意识到的问题是,在从闭包返回 MOC 并在
RootViewController

中设置之前,正在使用

AppDelegate
中的 MOC。我在这里做什么?


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

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.