【问题标题】:Multithreaded Core Data object has empty properties on main thread after saving on background thread多线程核心数据对象在后台线程上保存后在主线程上具有空属性
【发布时间】:2019-09-27 13:42:26
【问题描述】:

如标题所述,我在后台线程保存对象后,其属性在主线程上为空,例如字符串为"",数字为0等。

这里有一些代码!

用户类别:

@objc(User)
class User: NSManagedObject {

    @NSManaged var id: Int32
    @NSManaged var email: String
}

进行实际保存的用户存储库:

func saveUser(fromJSON json: Any, onSuccess: ((User) -> Void)?, onFailure: ((Error) -> Void)?) {
    dataManager.persistentContainer.performBackgroundTask { context in
        context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
        let user = self.userFactory.user(fromJSON: json, inContext: context)
        // print(user.id) and print(user.email) output correct data
        do {
            try context.save()
            DispatchQueue.main.async {
                // print(user.id) and print(user.email) show 0 and ""
                onSuccess?(user)
            }
        } catch let error {
            DispatchQueue.main.async {
                onFailure?(error)
            }
        }
    }
}

dataManager 的配置如下:

class CoreDataManager {

    private let modelName: String

    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: modelName)
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }

            container.viewContext.automaticallyMergesChangesFromParent = true
            container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
        })
        return container
    }()

    init(modelName: String) {
        self.modelName = modelName
    }
}

我猜userFactory 不是那么重要,但为了完整起见:

class UserFactory {

    func user(fromJSON json: Any, inContext context: NSManagedObjectContext) -> User {
        guard
            let jsonDictionary = json as? [String: Any?],
            let userDictionary = jsonDictionary["user"] as? [String: Any?],
            let id = userDictionary["id"] as? Int32,
            let email = userDictionary["email"] as? String
        else {
            fatalError("Could not parse User object.")
        }

        let user = User(context: context)
        user.id = id
        user.email = email
        return user
    }
}

请参阅上面 sn-ps 中有关打印语句的 cmets,这些语句指出了用户属性正常的位置以及它们显示 0"" 的位置。

我知道我无法在线程之间传递 NSManagedObjects,但即使我尝试使用 DispatchQueue.main.async 块中的 objectID (NSManagedObjectID) 获取用户对象,用户属性仍然是空的。

我想我做错了什么,但不能指手画脚。任何建议将不胜感激。

提前非常感谢!

【问题讨论】:

  • 当你说user.id = iduser.email = email时,你确定这些不是nil吗?
  • 是的,我有 - 在上面的守卫声明中,在工厂里。
  • 哦,是的,我明白了,愚蠢的我,对不起。 - 顺便说一句,我真的很喜欢格式化guard 列表的方式。我将在我自己的代码中采用它!
  • 谢谢你,很高兴你喜欢它!是的,我认为当有超过 1 个语句时它很容易阅读。

标签: swift core-data ios10


【解决方案1】:

我认为,问题在于 Core Data 不是线程安全的。我知道你知道这一点——毕竟,这就是你创建特殊背景上下文的原因——但它具有非常严格的含义。你是说:

dataManager.persistentContainer.performBackgroundTask { context in
    context.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
    let user = self.userFactory.user(fromJSON: json, inContext: context)
    do {
        try context.save()
        DispatchQueue.main.async {
            print(user) // <--

你不能那样做。您仍在谈论您保存到后台上下文中的user,但您已经切换了线程。不可以。你不能从两个不同的线程谈论这个user

一旦你进入主线程,你可以与 Core Data 对话的唯一方式就是通过 main 上下文。您需要从 main 上下文中通过其 id fetch 用户。现在您可以查看它是如何填充的。

【讨论】:

  • 看看developer.apple.com/documentation/coredata/…。它用大写字母表示,“不要在队列之间传递 NSManagedObject 实例”。但在我看来,这正是你正在做的事情。
  • 谢谢@matt!是的,它实际上需要一个单独的NSFetchRequestuser.id(之前存储在一个变量中)。我希望只要去privateContext.object(withID: user.objectID)viewContext.object(withID: user.objectID) 就足够了。如果我存储例如它似乎不是最佳的。 1000 个对象,然后必须立即从 Core Data 重新查询它们——但我认为它就是这样。感谢你的宝贵时间!干杯:)
  • @imilakovic,我想知道你是怎么解决的。我遇到了与第一次在 CoreData 中使用 bakground 上下文相同的问题,我将 CoreData 中的内容保存在后台上下文中,然后推回主线程以反映 UI 更改。但是 UI(tableView) 虽然属性有值,但什么也没显示。
  • @ChuckZHB 我想我从来没有这样做过。我为那个项目切换到 Realm :) 然而,一个应该解决问题的想法是将 CoreData 对象视为持久性对象 - 然后,当你在后台线程上获取它们时,你可以将它们映射到普通的 Swift 对象(可能structs?) 它不会关心它们所在的线程。然后您可以将这些映射对象传递回主线程并更新您的 tableView。希望这有效/有帮助!
  • @imilakovic 感谢您的回复。好吧,我在互联网上做了很多关于它的搜索。幸运的是,我可以将这些东西逐个组合起来并提出解决方案。一般来说,在 Core Data 中做多线程事情的正确方法,Apple 建议的方法是使用私有队列上下文并将其子类化到主队列上下文。这样我们就可以将后台队列的变化传输到主队列。如果您仍然感兴趣,我在我的问题https://stackoverflow.com/a/68031628/10158398 中给出了一个代码示例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多