并发的神奇记录
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:
来合并它)。这有很多优点,例如:
另一边:
注意合并策略 (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 的设计模式。它们定义了可以使用的模式,还提供了使用说明和示例。我没有使用所有这些,也没有阅读整本书,但它有助于从不同的角度看待事物:)以上是关于并发的神奇记录的主要内容,如果未能解决你的问题,请参考以下文章