SwiftUI 应用程序中上下文保存的核心数据 Objective-C 异常

Posted

技术标签:

【中文标题】SwiftUI 应用程序中上下文保存的核心数据 Objective-C 异常【英文标题】:Core Data Objective-C Exception on Context Save in SwiftUI App 【发布时间】:2021-03-09 04:59:33 【问题描述】:

当我在 ios 设备上全新安装 SwiftUI 应用时,Core Data 首次尝试保存时出现异常。

我的应用委托调用具有此 CloudKit 调用的函数:

iCloud.container.fetchUserRecordID() recordID, error in
  DispatchQueue.main.async 
    if let recordID = recordID
      let context = PersistenceController.shared.container.viewContext
      //...

      do
        try context.save() //<-- <!!!> Thread 1: hit Objective-C exception <!!!>
      catch
        print("fetchiCloudUserId CD Error")
        print(error)
      
    
  

关于崩溃的唯一其他信息在 Xcode 中:

我的猜测是,不知何故,Core Data 还没有准备好。我的PersistenceController 看起来像这样:

struct PersistenceController 
  static let shared = PersistenceController()
  let container: NSPersistentContainer

  init()
    container = NSPersistentContainer(name: "Avid")
    
    container.loadPersistentStores(completionHandler:  storeDescription, error in
      if let error = error as NSError? 
        print("=========\nUnresolved error \(error), \(error.userInfo)")
      
    )
  

我在设备上再次重建我的应用程序后,不会发生异常。有什么想法吗?

【问题讨论】:

虽然它不应该有太大区别,因为您已经在主队列中访问 viewContext(ViewContext 无论如何都由主队列支持)尝试在上下文中使用 performperformAndWait 并避免使用手动线程上下文切换(.DispatchQueue.main.async)你能不能也打印错误,提供的数据不足以进一步挖掘 没有错误可以打印,因为它没有捕捉到。它只是说Thread 1: hit Objective-C exception 并挂起。 这很奇怪:|您可以尝试捕获objective-C异常吗,swift try catch实际上并没有捕获/处理异常,它只处理错误:)如果您需要知道如何在swift中捕获objective-C异常,请尝试***.com/questions/66346312/crash-detect-swift/…我宁愿尝试执行context.save() 在objective-c 块中,看看我是否可以捕获任何可能能够更好地了解正在发生的事情的异常 “不知何故 Core Data 还没有准备好” - 是的,最初设置 CoreData 需要一些时间。您需要确保仅在初始化完成后才使用 CoreData。不过,该解决方案可能看起来很麻烦:使用最初暂停的调度队列,在其中执行 all CoreData 调用。在完成处理程序中恢复它,它会告诉您 CoreData 已准备好。或者使用一些肮脏的技巧,让您第一次使用 CoreData 等待一段时间 - 丑陋,但是...... ;) 【参考方案1】:

如果您的问题源于某些数据竞争问题,CoreData 还没有准备好执行请求,您可以使用以下解决方案:

struct PersistenceController 
    static let shared = PersistenceController()

    private let _container: NSPersistentContainer
    private let sem = DispatchSemaphore(value: 0)

    var container: NSPersistentContainer 
        get 
            sem.wait()
            sem.signal()
            return _container
        
    
    init() 
      _container = NSPersistentContainer(name: "Avid")
      let sem = self.sem
      _container.loadPersistentStores(completionHandler:  storeDescription, error in
        sem.signal()
        if let error = error as NSError? 
          print("=========\nUnresolved error \(error), \(error.userInfo)")
        
      )
    

它的作用是,它使用一个最初不满足的信号量来同步等待底层的NSPersistentContainer 值变为有效。

注意:当您阅读synchronously 时,您应该立即怀疑该解决方案的实用性!

信号量将在完成处理程序中发出信号,从而启用对 NSPersistentContainer 值的后续访问。

然而,这种方法有很大的注意事项:

它看起来不太漂亮(也表明有些地方不对劲) 同步等待容易出现死锁 当您的 CoreData 无法初始化时,无论如何都会崩溃 它会阻塞调用container 属性的任何线程。

优点是:重量轻、易于实施且易于理解。 请注意,当应用程序最初启动时,我们真的只需要一次。那么,我们就不需要所有这些东西了。

恕我直言,更好的解决方案是使用我的评论中描述的队列。仍然不理想 - 切换队列会增加很多开销。

另一种方法可能会使用 Swift Combine,它会返回一些 Publisher,您可以在其中执行 CoreData 调用。这当然是关于健壮性和处理错误的机会的最佳方法,但也非常复杂并增加了很多开销,这实际上只需要在我们第一次启动应用程序后才需要一次。

【讨论】:

感谢您对此提供的帮助。我尝试了您建议的信号量方法,但发生了同样的崩溃。事实上,Core Data 似乎已经准备就绪。它还没有准备好保存上下文。我在崩溃点之前尝试了一个获取请求,它工作正常。 ? 太糟糕了 ;) 要了解发生了什么,我们需要一份详细的崩溃报告。您能在崩溃报告中找到任何其他附加信息吗?至少完整的回溯。另外,启用 CoreData Debugging、Thread-Sanitizer【参考方案2】:

非常感谢@CouchDeveloper 让我走上正轨。我不知道我可以让 Xcode 透露更多关于这种模糊异常的信息。我与另一位开发人员交谈并了解到我可以设置一个符号断点来告诉我它崩溃的原因。这就是我所做的。

在断点导航器(Xcode 中的左侧列选项卡之一)中创建一个新的符号断点

符号: objc_exception_throw

条件

(BOOL)[(NSString *)[[(id)$arg1 class] className] isEqualToString:@"_NSCoreDataOptimisticLockingException"] == 0

行动Debugger Command

po $arg1

设置完成后,控制台显示我在设置的唯一字段上存在约束冲突。我能够对此采取行动并修复它。

【讨论】:

以上是关于SwiftUI 应用程序中上下文保存的核心数据 Objective-C 异常的主要内容,如果未能解决你的问题,请参考以下文章

在 SwiftUI 主体中出错的子上下文上创建核心数据实体

SwiftUI:如何从核心数据中的 TextField 中保存值

构造函数中上下文类的依赖注入

崩溃前保存核心数据上下文

不理解 Java EE 中上下文的概念

在哪里保存核心数据上下文...?