并发的神奇记录

Posted

技术标签:

【中文标题】并发的神奇记录【英文标题】:Magical Record with concurrency 【发布时间】:2015-05-27 20:20:25 【问题描述】:

在使用 Core Data 和 Magical Record 工作了一段时间后,我正在开发一个 ios 应用程序并出现错误:

error: NULL _cd_rawData 但对象没有变成故障

在这个项目之前我并不了解 Core Data,事实证明我非常天真地认为我可以使用 Magical Record 而不用担心并发性,因为我没有专门针对托管上下文的任何想法/工作主线程和后台线程。

在大量阅读有关 Core Data Managed Object Contexts 和 Magical Record 之后,我了解到:

NSManagedObjects 不是线程安全的。 NSManagedObjectId 是线程安全的。 我可以使用:Entity *localEntity = [entity MR_inContext:localContext] 的 Magical Record 在后台线程上下文中处理实体。 我应该使用 Magical Record 的 saveWithBlock:completion:saveWithBlockAndWait: 方法来获取托管上下文以用于后台线程。

关于我的申请的一些信息:

我正在使用最新版本的 Magical Record 2.2。 我有一个后端服务器,我的应用程序经常与它通信。 它们的通信类似于 Whatsapp,因为它使用后台线程与服务器通信并在成功响应后更新托管对象。 我使用 DataModel 对象包装模型,这些对象将托管对象保存在数组中,以便快速引用 UI/后台使用。

现在 - 我的问题是:

    我应该只从 UI 线程获取吗?我可以将托管对象保存在 DataModel 对象中吗? 如何从后台线程创建新实体并在 DataModel 对象中使用新创建的实体? 是否有我应该使用的最佳设计方案?特别是在向服务器发送请求并获得响应时,我是否应该创建一个新的托管上下文并在整个线程的活动中使用它?

如果一切都清楚,请告诉我。如果没有,我会尝试增加清晰度。

任何帮助或指导将不胜感激。

谢谢!

【问题讨论】:

【参考方案1】:

我没有与 MagicalRecord 合作,但这些问题与 CoreData 的关系比与 MagicalRecord 的关系更大,所以我会尝试回答它们:)。

1) 从主(UI)线程获取

有很多方法可以设计应用程序模型,所以我使用 CoreData 几年来学到了两件重要的事情:

在处理 UI 时,始终在主线程上获取对象。正如您正确指出的那样,NSManagedObjects 不是线程安全的,因此您不能(嗯,可以,但不应该)从不同的线程访问它们的数据。 NSFetchedResultsController 是您需要显示长列表时最好的朋友(例如,用于消息 - 但要注意请求的 batchSize)。

您应该将存储和提取设计为快速且响应迅速。使用索引、只获取需要的属性、预取关系等。

另一方面,如果您需要从大量数据中获取,您可以在不同的线程上使用上下文并仅传输 NSManagedObjectID。假设您的用户有大量消息,并且您想向他展示来自特定联系人的最新 10 条消息。您可以创建后台上下文(私有并发),获取这 10 个消息 ID (NSManagedObjectIDResultType),将它们存储在数组中(或任何其他适合您的格式),将它们返回到您的主线程并仅获取这些 ID。请注意,如果由于 predicate/sortDescriptor 而导致 fetch 需要很长时间,则这种方法会加快速度,而不是如果“问题”正在将故障转化为对象的过程中(例如,存储在可转换属性中的大型 UIImage :))

2) 在后台创建实体

您可以在后台上下文中创建对象,将其存储为 NSManagedObjectID 保存上下文后(对象在保存之前只有临时 ID)并将其发送回您的主线程,您可以在其中执行获取ID 并在您的主要上下文中获取对象。

3) 使用背景上下文

我不知道这是否是最好的,但我对 NSManagedObjectContext 观察和通知合并非常满意。查看: mergeChangesFromContextDidSaveNotification:

因此,您创建后台上下文,将主上下文添加为更改的观察者 (NSManagedObjectContextObjectsDidChangeNotification),后台上下文会自动向您发送有关所有更改的通知(每次执行保存时)——插入/更新/删除对象(不担心,您可以通过调用mergeChangesFromContextDidSaveNotification: 来合并它)。这有很多优点,例如:

所有内容都会自动更新(您在“观察上下文”中获取的每个对象都会更新/删除) 每个合并都在内存中运行(没有提取,没有在主线程上持久化) 如果您实现 NSFetchedResultsController 的委托方法,所有内容都会自动更新(并非所有内容 - 见下文)

另一边:

注意合并策略 (NSMangedObjectContext mergePolicy) 注意引用从后台(或只是另一个上下文)删除的托管对象 NSFetchedResultsController 仅在更改“直接”属性时更新(结帐this SO question)

嗯,我希望它能回答你的问题。如果一切顺利,请不要犹豫:)

关于子上下文的附注

还可以查看子上下文。他们也可以很强大。基本上每个子上下文都会在保存时将其更改发送到父上下文(如果是“基本”上下文(没有父上下文),它会将其更改发送到持久协调器)。

例如,当您创建编辑/添加控制器时,您可以从主上下文创建子上下文并在其中执行所有更改。当用户决定取消操作时,您只需销毁(删除引用)子上下文,不会存储任何更改。如果用户决定接受他/她所做的更改,则保存子上下文并将其销毁。通过保存子上下文,所有更改都会传播到它的父存储(在本例中是您的主上下文)。只需确保还保存父上下文(在某些时候)以保留这些更改(保存:方法不执行冒泡)。结帐documentation of managing parent store。

编码愉快!

【讨论】:

哇,这个解释我不能要求更多。非常感谢!如果你在做,我还有一个问题:你认为 UI 使用 CoreData/MagicalRecord 元素的最佳实践是什么?我在中间使用了一个层,我称之为 DataModel,UI 与之对话,它与 CoreData 元素和后端服务器对话。现在我在想我可能应该改变我的设计,因为你对添加/编辑 UI 页面的建议需要新的托管上下文。有什么建议吗? 哦,顺便说一句,如果我可以问,你为什么不使用 Magical Record? 嗯,主要原因可能是“我还没有切换,我很懒惰”:D。几年前,我犯了一个错误,认为 MagicalRecord 是基于 ActiveRecord 模式(这对 IMO 来说对 CoreData 非常不利)。我创建了自己的小帮手(更多信息请参阅github.com/jakubknejzlik/GNContextManager)。现在我有点忙于学习足够的东西来切换到 MR。但无论如何,它是一个有很多功能的好工具:) 感谢您的赞美,但我还有很多东西要学:)。我认为将逻辑分成几个部分总是更好。 IMO 的关键点是将部分粘在一起。因此,如果您有与服务器通信的东西 A 创建 AAdapter,则为服务器 B 创建 BAdapter。如果您的应用可以打印,请创建 PrintManager 并为每台打印机创建适配器等。 我建议您查看 Gang of Four 的设计模式。它们定义了可以使用的模式,还提供了使用说明和示例。我没有使用所有这些,也没有阅读整本书,但它有助于从不同的角度看待事物:)

以上是关于并发的神奇记录的主要内容,如果未能解决你的问题,请参考以下文章

关于:清除并发请求和(或)管理器数据 请求的理解

imooc课程:Java高并发秒杀API 记录

Oracle EBS 清除并发请求和(或)管理器数据 请求

MySQL MVCC(多版本并发控制)

[记录]Python高并发编程

Centos7.2高并发优化记录