【问题标题】:Core Data multi thread applicationCore Data 多线程应用
【发布时间】:2011-01-09 10:38:13
【问题描述】:

我正在尝试以多线程方式使用核心数据。 我只是想在后台下载新数据时向应用程序显示先前下载的数据。 这应该让用户在更新过程中访问应用程序。

我有一个 NSURLConnection,它使用委托异步下载文件(并显示进度),然后我使用 XMLParser 解析新数据并在单独的上下文中创建新的 NSManagedObjects,具有自己的 persistentStore 并使用单独的线程。

问题是在旧对象的相同上下文中创建新对象同时显示它可能会引发 BAD_INSTRUCTION 异常。 因此,我决定为新数据使用单独的上下文,但我无法找到一种方法在完成后将所有对象移动到另一个上下文。

保罗又名慢树

【问题讨论】:

    标签: ios iphone multithreading core-data


    【解决方案1】:

    The Apple Concurrency with Core Data documentation 是开始的地方。看的真仔细……被我的误会咬了好多次!

    基本规则是:

    1. 每个程序使用一个NSPersistentStoreCoordinator。每个线程都不需要它们。
    2. 为每个线程创建一个NSManagedObjectContext
    3. 切勿将线程上的NSManagedObject 传递给另一个线程。
    4. 相反,通过-objectID 获取对象 ID 并将其传递给其他线程。

    更多规则:

    1. 确保在获取对象 ID 之前将对象保存到存储中。在保存之前,它们是临时的,您无法从其他线程访问它们。
    2. 如果您从多个线程更改托管对象,请注意合并策略。
    3. NSManagedObjectContext-mergeChangesFromContextDidSaveNotification: 很有帮助。

    但是让我再说一遍,请仔细阅读文档!真的很值得!

    【讨论】:

    • 我在 CoreDataBooks (mergeChangesFromContextDidSaveNotification) 中找到了一个很好的合并上下文的例子。非常感谢。祝你今天过得愉快。保罗又名慢树
    • 哦,感谢上帝。我读到这篇文章解决了我的问题。在后台线程中导入大量数据并获得大量不可预测的异常。在后台线程中实例化上下文而不是传递它似乎已经解决了我的问题。
    • 此文档尚未更新以利用 iOS 5 中非常重要的改进 - 我在答案中链接到的视频现在是更好的参考。
    • @JoãoNunes 不幸的是,您无法可靠地读取另一个线程上的对象 - 它有时似乎可以工作,但如果您导致加载错误的对象,则您(充其量)调用未定义的行为。
    • 不幸的是,Core Data 编程指南中关于 Concurrency with Core Data 的部分已经严重过时了。它描述了线程限制,它已经过时(并且已经过时了好几年)。
    【解决方案2】:

    我希望这可以帮助所有在多线程环境中使用核心数据遇到问题的人。

    查看苹果文档中的“Top Songs 2”。有了这段代码,我吃了 Matrix 的“红色药丸”,发现了一个新世界,没有双重免费错误,也没有错误。 :D

    希望这会有所帮助。

    保罗

    附言 非常感谢 Yuji,在你上面描述的文档中我找到了那个例子。

    【讨论】:

    • 你应该分享关于“热门歌曲 2”的链接
    【解决方案3】:

    目前 [2015 年 5 月] Apple Concurrency with Core Data documentation 充其量是非常具有误导性的,因为它没有涵盖 iOS 5 中的任何增强功能,因此不再显示同时使用核心数据的最佳方法。 iOS 5 中有两个非常重要的变化 - 父上下文和新的并发/线程类型。

    我还没有找到任何全面涵盖这些新功能的书面文档,但WWDC 2012 video "Session 214 - Core Data Best Practices" 确实很好地解释了这一切。

    Magical Record 使用了这些新功能,可能值得一看。

    真正的基础仍然是一样的——你仍然可以只使用创建托管对象上下文的线程。

    您现在可以使用 [moc performBlock:] 在正确的线程上运行代码。

    不再需要使用 mergeChangesFromContextDidSaveNotification:;而是创建一个子上下文来进行更改,然后保存子上下文。保存子上下文会自动将更改推送到父上下文中,并将更改保存到磁盘只需在其线程中对父上下文执行保存即可。

    为此,您必须使用并发类型创建父上下文,例如:

    mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    

    然后在后台线程上:

    context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
    [context setParentContext:mainManagedObjectContext];
    
    <... perform actions on context ...>
    
    NSError *error;
    if (![context save:&error])
    {
        <... handle error ...>
    }
    [mainManagedObjectContext performBlock:^{
        NSError *e = nil;
        if (![mainContext save:&e])
        {
            <... handle error ...>
        }
    }];
    

    【讨论】:

    • 支持最新信息。 SO 应该实施机制来促进涉及新技术开发的答案,并淡化那些过时的公认答案。感谢您推荐 Magic Record 框架。它的文档看起来非常好。稍后我可能会尝试。
    • 与您的代码 sn-p 相反,UIManagedDocument 默认在私有队列中创建其父上下文,在主队列中创建子上下文。知道为什么苹果会这样做吗?这完全是任意的吗?
    • 谢谢,已更正了 WWDC 链接。我没有使用过 UIManagedDocument,但在私有线程上拥有最顶层的上下文是一种常见模式,因此保存操作不会阻塞 ui - 如果您有大量数据要更新,该操作可能需要一段时间.
    • 我得到 Can only use -performBlock: on an NSManagedObjectContext that was created with a queue? initWithConcurrancyType 不应该是 privateQueue 吗?
    • 如果您想查看更新的并发指南,请复制这些雷达错误:openradar.me/radar?id=3166402openradar.me/radar?id=3166401
    猜你喜欢
    • 2011-05-27
    • 1970-01-01
    • 2012-08-07
    • 2010-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-21
    相关资源
    最近更新 更多