CoreData的performBackgroundTask方法崩溃

Car*_*ung 3 core-data swift

我编写了一个在CoreData中获取数据库的函数。此函数将关闭并运行performBackgroundTask以获取数据。然后,将结果传递给闭包以运行。我在其中编写了静态属性AppDelegateviewContext方便访问:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    static var persistentContainer: NSPersistentContainer {
        return (UIApplication.shared.delegate as! AppDelegate).persistentContainer
    }

    static var viewContext: NSManagedObjectContext {
        return persistentContainer.viewContext
    }
    // ...
}
Run Code Online (Sandbox Code Playgroud)

以下是我编写的使用以下函数崩溃的函数(不是方法)context

func fetch<T>(fetchRequest: NSFetchRequest<T>, keyForOrder: String? = nil, format: String? = nil, keyword: String? = nil, handler: (([T]?)->Void)? = nil) where T:NSManagedObject, T: NSFetchRequestResult {
    AppDelegate.persistentContainer.performBackgroundTask{(context: NSManagedObjectContext) in
        if let format = format?.trimmingCharacters(in: .whitespacesAndNewlines),
            !format.isEmpty,
            let keyword = keyword?.trimmingCharacters(in: .whitespacesAndNewlines),
            !keyword.isEmpty {
            fetchRequest.predicate = NSPredicate(format: format, keyword)
        }

        if let keyForOrder = keyForOrder {
            fetchRequest.sortDescriptors = [NSSortDescriptor(key: keyForOrder, ascending: true)]
        }

        guard let cats = try? context.fetch(fetchRequest) else { // crash
            return
        }

        context.performAndWait(){ // crash
            if let handler = handler {
                handler(cats)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,如果我替换contextAppDelegate.viewContext,该函数将不会崩溃:

func fetch<T>(fetchRequest: NSFetchRequest<T>, keyForOrder: String? = nil, format: String? = nil, keyword: String? = nil, handler: (([T]?)->Void)? = nil) where T:NSManagedObject, T: NSFetchRequestResult {
    AppDelegate.persistentContainer.performBackgroundTask{(context: NSManagedObjectContext) in
        if let format = format?.trimmingCharacters(in: .whitespacesAndNewlines),
            !format.isEmpty,
            let keyword = keyword?.trimmingCharacters(in: .whitespacesAndNewlines),
            !keyword.isEmpty {
            fetchRequest.predicate = NSPredicate(format: format, keyword)
        }

        if let keyForOrder = keyForOrder {
            fetchRequest.sortDescriptors = [NSSortDescriptor(key: keyForOrder, ascending: true)]
        }

        guard let cats = try? AppDelegate.viewContext.fetch(fetchRequest) else { // crash
            return
        }

        AppDelegate.viewContext.performAndWait(){ // crash
            if let handler = handler {
                handler(cats)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

到底是怎么回事?

谢谢。

Jon*_*ose 5

这里有一些问题:

  • performBackgroundTask已经在上下文的正确线程上,因此没有理由调用它,context.performAndWait并且可能导致死锁或崩溃。
  • 在PerformBackgroundTask中获取或创建的项目在任何情况下都不能离开该块。上下文将在块末尾被破坏,并且ManagedObjects在尝试访问其上下文时将崩溃
  • 管理核心数据线程安全可能很困难,而且我发现,除非对象的上下文明确且明确,否则从不将托管对象传递或返回给函数是一种普遍的好习惯。这不是牢不可破的规则,但是我认为在编写API时这是一个很好的经验法则。
  • performBackgroundTask通常用于更新核心数据。如果您仅在进行访存,则应使用viewContext。通常仅在后台进行获取以将其传递给主线程是一种浪费。
  • 在performBackgroundTask块中,您无法访问viewContext-既不能读取也不能写入。如果这样做,该应用程序可能会在任何时候崩溃,导致崩溃报告混乱,甚至在以后不破坏线程安全的情况下。
  • 我不知道您要创建的谓词是什么样子,但是我强烈感觉到它们是错误的。提取时会导致崩溃。

总的来说,我认为您创建的功能几乎没有价值。如果它所做的只是获取,则您只需创建谓词和排序描述符,然后在viewContext上获取。如果您坚持要保留该函数,则删除performBackgroundTask,使用viewContext进行获取,返回结果(而不是回调),并且只能从主线程调用它。