ASJCoreDataOperation
将并发/多线程添加到 CoreData
不是非常直接和明显。主要问题是与 NSManagedObjectContext
有关,它是线程不安全的。在 AppDelegate
中创建的默认一个是创建在主线程上的
Swift
var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
Objective-C
NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
使用 AppDelegate
的管理对象上下文的主要缺点是,每当进行保存、检索或删除操作时,主线程/UI 都会被阻塞。如果您正在进行小型操作,这可能不会引起注意。但面对大型的操作,它将造成问题。
解决方案是在后台执行此类 CoreData
操作,只有在您需要进行任何 UI 变更,比如说重新加载表格时,才在主队列上调用 reloadData
。
安装
CocoaPods是安装此库的首选方式。将以下命令添加到您的Podfile
Swift
pod 'ASJCoreDataOperation'
Objective-C
pod 'ASJCoreDataOperation/Obj-C'
背景
- 键:
NSManagedObjectContext
=moc
并发选项
有三种并发类型
NSConfinementConcurrencyType
(从iOS 9.0开始已标记为弃用)NSPrivateQueueConcurrencyType
NSMainQueueConcurrencyType
您不应再使用NSConfinementConcurrencyType
,因为它已经过时,并且苹果公司不推荐使用。 NSPrivateQueueConcurrencyType
在后台线程上创建一个moc
,而NSMainQueueConcurrencyType
则在主线程上创建一个。我们感兴趣的是NSPrivateQueueConcurrencyType
。
NSManagedObjectContext
创建您可以创建任意数量的 moc
。在保存过程中,它必须经过一个 NSPersistentStoreCoordinator
向 sqlite 文件写入数据。您可以使用在 AppDelegate
中实现的那个,或者提供自己的。只需确保无论您的应用程序具有哪种类型的存储 NSSQLiteStoreType
、NSXMLStoreType
、NSBinaryStoreType
或 NSInMemoryStoreType
,协调对象都必须绑定到相同的目的地。
Swift
var privateMoc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateMoc.persistentStoreCoordinator = appDelegatesPersistentStoreCoordinator;
Objective-C
NSManagedObjectContext *privateMoc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
privateMoc.persistentStoreCoordinator = appDelegatesPersistentStoreCoordinator;
有 moc
的两种方法,分别是 performBlock:(void (^)(void))block
和 performBlockAndWait:(void (^)(void))block
。在任何这些块中编写的代码都 保证 在创建 moc
的同一队列上执行。您必须在其中一个方法中编写您的 CoreData
逻辑。两者的区别在于,performBlockAndWait:
将在操作完成后阻塞队列。
在私有队列上保存
当在私有 moc
上发生 save
操作时,数据将被写入 sqlite 文件,但主队列将不会收到通知。如果您在主队列上设置了一个 NSFetchedResultsController
,控制将 不会 到达其代理方法。然而,如果 CoreData
操作和 NSFetchedResultsController
使用相同的 moc
,则它将工作。
如果您需要主队列通知到私有上下文中所做的任何更改,您需要从私有 moc
合并那些更改到主 moc
。为此,您必须在私有 moc
上开始监听 NSManagedObjectContextDidSaveNotification
。
Swift
NotificationCenter.default.addObserver(self, selector: #selector(contextDidSave(note:)), name: .NSManagedObjectContextDidSave, object: privateMoc)
Objective-C
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextDidSave:) name:NSManagedObjectContextDidSaveNotification object:privateMoc];
在 contextDidSave:
中,我们需要将私有 moc
的更改合并到主 moc
中。您必须使用 moc
的 performBlock:
或 performBlockAndWait:
方法,以确保合并在正确的线程上发生。
Swift
func contextDidSave(note: Notification)
{
mainMoc.perform { () -> Void in
self.mainMoc.mergeChanges(fromContextDidSave: note)
}
}
Objective-C
- (void)contextDidSave:(NSNotification *)note
{
[mainMoc performBlock:^{
[mainMoc mergeChangesFromContextDidSaveNotification:note];
}];
}
note
对象包含对持久化对象所做的所有修改的信息。然而,当在两个 moc
之间合并数据时可能会出现问题,并且可能会发生冲突。因此,您必须提供一个 mergePolicy
,以便 CoreData
能够了解如何解决它们。
Swift
privateMoc.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
Objective-C
privateMoc.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
- 注意:还有另一种并发模式,使用子
moc
和父moc
,它具有更简单的设置,但不太推荐使用,因为它会阻塞主线程。
这个库的功能
ASJCoreDataOperation
是 Operation
/NSOperation
的一个子类,它默认提供私有队列支持。此类设计为可以扩展使用,并**必须继承使用**才能正常工作。
Swift
convenience init(privateMoc: NSManagedObjectContext!, mainMoc: NSManagedObjectContext?)
Objective-C
- (instancetype)initWithPrivateMoc:(nullable NSManagedObjectContext *)privateMoc mainMoc:(nullable NSManagedObjectContext *)mainMoc NS_DESIGNATED_INITIALIZER;
这是创建子类实例的建议方法。您可以在两个参数中都传递nil
或使用init
方法。在这种情况下,将创建一个私有 moc,并使用 AppDelegate
的 moc
作为 mainMoc
。如果您的 AppDelegate
没有拥有 moc
对象,您必须在主队列上提供一个。
Swift
public var privateMoc: NSManagedObjectContext!
Objective-C
@property (readonly, strong, nonatomic) NSManagedObjectContext *privateMoc;
无论私有 moc
以何种方式创建,它都会公开暴露并供您使用,例如绑定 NSFetchedResultsController
并进行异步检索。
Swift
public var saveBlock: (() -> Void)?
Objective-C
@property (copy) SaveBlock saveBlock;
当保存操作完成时触发的一个代码块。
Swift
public func coreDataOperation()
Objective-C
- (void)coreDataOperation;
您需要在子类中重写此方法。您希望执行的任何 CoreData
操作都应该在这里编写。库将确保在此正确线程上调用此方法。
使用方法
Swift
var operationQueue = OperationQueue()
let operation = SomeCoreDataOperationSubclass(privateMoc: somePrivateMoc, mainMoc: nil)
operationQueue.addOperation(operation)
Objective-C
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
SomeCoreDataOperationSubclass *operation = [[SomeCoreDataOperationSubclass alloc] initWithPrivateMoc:somePrivateMoc mainMoc:nil];
[operationQueue addOperation:operation];
一旦将 operation
添加到 operationQueue
,它将在后台队列上开始运行。您可以使用 completionBlock
属性来获取操作完成时的事件。
鸣谢
- Shashank Pali 感谢其在示例项目中修复 UI 问题。
- Manish Pathak 的激励。
- Core Data 编程指南 - 并发
- 从头开始的核心数据:并发
- 网络化核心数据应用程序
- 常见后台实践
- 导入大型数据集
- Swift中iOS无法识别的选择器发送给实例
待办事项
完成块,以便在操作完成后得知。- 一种在操作中途取消操作的方法。
许可证
ASJCoreDataOperation
在MIT许可证下可用。有关更多信息,请参阅LICENSE文件。