并发的神奇记录
Magical Record with concurrency
我在使用 Core Data 和 Magical Record 工作了一段时间后遇到错误,现在正在开发 iOS 应用程序:
error: NULL _cd_rawData but the object is not being turned into a fault
在这个项目之前我不知道 Core Data,事实证明我非常天真地认为我可以只使用 Magical Record 而不必担心并发性,因为我没有投入任何 thoughts/work关于主线程和后台线程的托管上下文。
在大量阅读有关 Core Data Managed Object Contexts 和 Magical Record 的文章后,我了解到:
- NSManagedObjects 不是线程安全的。
- NSManagedObjectId 是线程安全的。
- 我可以使用:
Entity *localEntity = [entity MR_inContext:localContext]
of Magical Record 在后台线程上下文中处理实体。
- 我应该使用 Magical Record 的
saveWithBlock:completion:
和 saveWithBlockAndWait:
方法来获取托管上下文以用于后台线程。
关于我的申请的一些信息:
- 我使用的是最新版本的 Magical Record 2.2。
- 我有一个后端服务器,我的应用程序经常与之通信。
- 它们的通信类似于 Whatsapp,因为它使用后台线程与服务器通信并在成功响应时更新托管对象。
- 我用 DataModel 对象包装模型,这些对象将托管对象保存在数组中,以便快速引用 UI/background 使用。
现在 - 我的问题是:
- 我应该只从 UI 线程中获取吗?我可以将托管对象保存在 DataModel 对象中吗?
- 如何从后台线程创建新实体并在 DataModel 对象中使用新创建的实体?
- 是否有我应该使用的最佳设计场景?特别是在向服务器发送请求并获得响应时,我是否应该创建一个新的托管上下文并在整个线程的 activity?
中使用它
如果一切都清楚,请告诉我。如果没有,我会尝试增加清晰度。
如有任何帮助或指南,我们将不胜感激。
谢谢!
我没有使用 MagicalRecord,但这些问题与 CoreData 的关系比与 MagicalRecord 的关系更密切,所以我会尽量回答它们:)。
1) 从主线程(UI) 获取
有很多方法可以设计应用程序模型,所以我使用 CoreData 几年来学到了两件重要的事情:
在处理 UI 时,始终在主线程上获取对象。正如您正确指出的那样,NSManagedObjects 不是 线程安全的,因此您不能(好吧,可以,但不应该)从不同的线程访问它们的数据。 NSFetchedResultsController 是您最好的朋友,当您需要显示长列表时(例如,对于消息——但要注意请求的 batchSize)。
您应该将存储和提取设计得快速且响应迅速。使用索引、只获取需要的属性、预取关系等
另一方面,如果您需要从大量数据中获取数据,您可以在不同线程上使用上下文并仅传输 NSManagedObjectID。假设您的用户有大量消息,您希望向他显示来自特定联系人的最新 10 条消息。您可以创建后台上下文(私有并发),获取这 10 个消息 ID(NSManagedObjectIDResultType
),将它们存储在数组(或任何其他适合您的格式)中,return 它们到您的主线程并获取那些仅 ID。请注意,如果由于 predicate/sortDescriptor 而需要很长时间获取,则此方法会加快速度,如果 "problem" 正在将故障转换为对象(例如,大 UIImage 存储在可转换属性中: ) )
2) 在后台创建实体
您可以在后台上下文中创建对象,保存它的 NSManagedObjectID 保存上下文后(对象在保存前只有临时 ID)并将其发送回您的主线程,其中您可以通过 ID 执行提取并在您的主要上下文中获取对象。
3) 使用背景上下文
我不知道它是否是最好的,但我对 NSManagedObjectContext 观察和通知合并非常满意。查看:
mergeChangesFromContextDidSaveNotification:
因此,您创建后台上下文,添加主上下文作为更改的观察者 (NSManagedObjectContextObjectsDidChangeNotification
),后台上下文会自动向您发送有关所有更改的通知(每次您执行保存时)– inserted/updated/deleted 对象(不用担心,您可以通过调用 mergeChangesFromContextDidSaveNotification:
来合并它)。这样有很多好处,比如:
- 一切都会自动更新(您在 "observing context" 中获取的每个对象都会得到 updated/deleted)
- 每次合并都在内存中运行(没有提取,没有在主线程上持久化)
- 如果你实现 NSFetchedResultsController 的委托方法,一切都会自动更新(不完全是一切——见下文)
另一边:
- 注意合并策略(NSMangedObjectContext mergePolicy)
- 注意引用从后台(或另一个上下文)删除的托管对象
- NSFetchedResultsController 仅在 "direct" 属性发生变化时更新(签出 this SO question)
嗯,我希望它能回答你的问题。如果一切顺利,请毫不犹豫地询问 :)
关于子上下文的附注
还可以查看子上下文。他们也可以很强大。基本上每个子上下文都会在保存时将它的更改发送到父上下文(在 "base" 上下文(没有父上下文)的情况下,它会将它的更改发送到持久协调器)。
例如,当您创建 edit/add 控制器时,您可以从主上下文创建子上下文并在其中执行所有更改。当用户决定取消操作时,您只需销毁(删除引用)子上下文,不会存储任何更改。如果用户决定接受所做的更改 he/she,请保存子上下文并将其销毁。通过保存子上下文,所有更改都会传播到它的父存储(在本例中是您的主上下文)。只需确保还保存父上下文(在某个时候)以保留这些更改(保存:方法不执行 冒泡 )。签出 documentation of managing parent store.
编码愉快!
我在使用 Core Data 和 Magical Record 工作了一段时间后遇到错误,现在正在开发 iOS 应用程序:
error: NULL _cd_rawData but the object is not being turned into a fault
在这个项目之前我不知道 Core Data,事实证明我非常天真地认为我可以只使用 Magical Record 而不必担心并发性,因为我没有投入任何 thoughts/work关于主线程和后台线程的托管上下文。
在大量阅读有关 Core Data Managed Object Contexts 和 Magical Record 的文章后,我了解到:
- NSManagedObjects 不是线程安全的。
- NSManagedObjectId 是线程安全的。
- 我可以使用:
Entity *localEntity = [entity MR_inContext:localContext]
of Magical Record 在后台线程上下文中处理实体。 - 我应该使用 Magical Record 的
saveWithBlock:completion:
和saveWithBlockAndWait:
方法来获取托管上下文以用于后台线程。
关于我的申请的一些信息:
- 我使用的是最新版本的 Magical Record 2.2。
- 我有一个后端服务器,我的应用程序经常与之通信。
- 它们的通信类似于 Whatsapp,因为它使用后台线程与服务器通信并在成功响应时更新托管对象。
- 我用 DataModel 对象包装模型,这些对象将托管对象保存在数组中,以便快速引用 UI/background 使用。
现在 - 我的问题是:
- 我应该只从 UI 线程中获取吗?我可以将托管对象保存在 DataModel 对象中吗?
- 如何从后台线程创建新实体并在 DataModel 对象中使用新创建的实体?
- 是否有我应该使用的最佳设计场景?特别是在向服务器发送请求并获得响应时,我是否应该创建一个新的托管上下文并在整个线程的 activity? 中使用它
如果一切都清楚,请告诉我。如果没有,我会尝试增加清晰度。
如有任何帮助或指南,我们将不胜感激。
谢谢!
我没有使用 MagicalRecord,但这些问题与 CoreData 的关系比与 MagicalRecord 的关系更密切,所以我会尽量回答它们:)。
1) 从主线程(UI) 获取
有很多方法可以设计应用程序模型,所以我使用 CoreData 几年来学到了两件重要的事情:
在处理 UI 时,始终在主线程上获取对象。正如您正确指出的那样,NSManagedObjects 不是 线程安全的,因此您不能(好吧,可以,但不应该)从不同的线程访问它们的数据。 NSFetchedResultsController 是您最好的朋友,当您需要显示长列表时(例如,对于消息——但要注意请求的 batchSize)。
您应该将存储和提取设计得快速且响应迅速。使用索引、只获取需要的属性、预取关系等
另一方面,如果您需要从大量数据中获取数据,您可以在不同线程上使用上下文并仅传输 NSManagedObjectID。假设您的用户有大量消息,您希望向他显示来自特定联系人的最新 10 条消息。您可以创建后台上下文(私有并发),获取这 10 个消息 ID(
NSManagedObjectIDResultType
),将它们存储在数组(或任何其他适合您的格式)中,return 它们到您的主线程并获取那些仅 ID。请注意,如果由于 predicate/sortDescriptor 而需要很长时间获取,则此方法会加快速度,如果 "problem" 正在将故障转换为对象(例如,大 UIImage 存储在可转换属性中: ) )
2) 在后台创建实体
您可以在后台上下文中创建对象,保存它的 NSManagedObjectID 保存上下文后(对象在保存前只有临时 ID)并将其发送回您的主线程,其中您可以通过 ID 执行提取并在您的主要上下文中获取对象。
3) 使用背景上下文
我不知道它是否是最好的,但我对 NSManagedObjectContext 观察和通知合并非常满意。查看: mergeChangesFromContextDidSaveNotification:
因此,您创建后台上下文,添加主上下文作为更改的观察者 (NSManagedObjectContextObjectsDidChangeNotification
),后台上下文会自动向您发送有关所有更改的通知(每次您执行保存时)– inserted/updated/deleted 对象(不用担心,您可以通过调用 mergeChangesFromContextDidSaveNotification:
来合并它)。这样有很多好处,比如:
- 一切都会自动更新(您在 "observing context" 中获取的每个对象都会得到 updated/deleted)
- 每次合并都在内存中运行(没有提取,没有在主线程上持久化)
- 如果你实现 NSFetchedResultsController 的委托方法,一切都会自动更新(不完全是一切——见下文)
另一边:
- 注意合并策略(NSMangedObjectContext mergePolicy)
- 注意引用从后台(或另一个上下文)删除的托管对象
- NSFetchedResultsController 仅在 "direct" 属性发生变化时更新(签出 this SO question)
嗯,我希望它能回答你的问题。如果一切顺利,请毫不犹豫地询问 :)
关于子上下文的附注
还可以查看子上下文。他们也可以很强大。基本上每个子上下文都会在保存时将它的更改发送到父上下文(在 "base" 上下文(没有父上下文)的情况下,它会将它的更改发送到持久协调器)。
例如,当您创建 edit/add 控制器时,您可以从主上下文创建子上下文并在其中执行所有更改。当用户决定取消操作时,您只需销毁(删除引用)子上下文,不会存储任何更改。如果用户决定接受所做的更改 he/she,请保存子上下文并将其销毁。通过保存子上下文,所有更改都会传播到它的父存储(在本例中是您的主上下文)。只需确保还保存父上下文(在某个时候)以保留这些更改(保存:方法不执行 冒泡 )。签出 documentation of managing parent store.
编码愉快!