CoreData的performBackgroundTask方法崩溃了

时间:2017-04-19 17:21:37

标签: swift core-data

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

@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
    }
    // ...
}

以下是我写的使用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)
            }
        }
    }
}

但如果我用context替换AppDelegate.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)
            }
        }
    }
}

到底发生了什么?

感谢。

1 个答案:

答案 0 :(得分:4)

以下是一些问题:

  • performBackgroundTask已经在上下文的正确线程上,因此没有理由调用context.performAndWait并可能导致死锁或崩溃。
  • 在performBackgroundTask中提取或创建的项目在任何情况下都不能保留该块 。上下文将在块结束时销毁,并且当托管对象尝试访问其上下文时将崩溃
  • 管理核心数据线程安全性可能很困难,我发现从不将托管对象传递给函数或将其返回给函数这是一种通常的好习惯,除非对象的上下文是明确且清晰的。这不是一个牢不可破的规则,但我认为在制作你的api时这是一个很好的经验法则。
  • performBackgroundTask通常用于更新核心数据。如果您只是在进行提取,则应使用viewContext。在后台进行提取只是为了将其传递给主线程通常是一种浪费。
  • 在performBackgroundTask块中,您无法访问viewContext - 无论是读取还是写入。如果您这样做,应用程序可能会随着崩溃的崩溃报告而崩溃,即使是在您没有违反线程安全的情况下也是如此。
  • 我不知道你创造的谓词是什么样的,但我有一种强烈的感觉,他们错了。这会在获取时导致崩溃。

总的来说,我认为你创造的功能没什么价值。如果它所做的只是一个fetch,那么你应该简单地创建谓词和排序描述符并在viewContext上获取。如果你坚持保留这个函数,那么删除performBackgroundTask,使用viewContext获取,返回结果(而不是回调),只从主线程中调用它。