您必须分批处理它们,其中每个批次都由单独的后台上下文获取,并且导出发生在该上下文的队列中。对于名为Event 的实体,这是您可以这样做的一种方法。一般的方法是获取您要导出的所有对象的对象 ID,然后将它们分成可以由单独的背景上下文处理的组。
由于托管对象不能跨队列工作,因此首先要获取对象 ID 并将它们分成多个批次。首先获取所有对象 ID。
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSFetchRequest<Event *> *fetchRequest = Event.fetchRequest;
fetchRequest.resultType = NSManagedObjectIDResultType;
NSError *fetchError = NULL;
NSArray<NSManagedObjectID *> *allObjectIDs = [context executeFetchRequest:fetchRequest error:&fetchError];
然后循环遍历带有子范围的数组。对于每个批次,创建一个新的背景上下文。使用该上下文来获取该批次对象 ID 的托管对象。然后处理导出托管对象。
NSInteger batchSize = 100;
NSRange currentRange = NSMakeRange(0, batchSize);
AppDelegate *appDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
NSPersistentContainer *persistentContainer = appDelegate.persistentContainer;
while (currentRange.location < allObjectIDs.count) {
NSArray<NSManagedObjectID *> *batchObjectIDs = [allObjectIDs subarrayWithRange:currentRange];
NSManagedObjectContext *batchContext = persistentContainer.newBackgroundContext;
[batchContext performBlock:^{
NSFetchRequest<Event *> *fetchRequest = Event.fetchRequest;
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"self in %@", batchObjectIDs];
NSError *fetchError = NULL;
NSArray <Event *> *batchEvents = [batchContext executeFetchRequest:fetchRequest error:&fetchError];
// Put your export code here, for the objects that were just fetched.
}];
currentRange.location += batchSize;
}
您应该尝试批量大小,看看哪种方法最适合您。
但这变得更棘手了,因为您的导出代码可能同时在多个队列上运行,并且您需要确保您的导出文件不会以乱七八糟的形式结束。解决这个问题的一种方法是使用NSFileCoordinator 来确保一次只允许一个队列写入。在上面的循环之前创建协调器:
NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] init];
然后上面的代码说要放置你的导出代码,做这样的事情:
[coordinator coordinateWritingItemAtURL:[self exportFileURL] options:0 error:&coordinatorError byAccessor:^(NSURL * _Nonnull newURL) {
NSFileHandle *exportHandle = [self createExportFileHandle];
for (Event *event in batchEvents) {
NSData *exportData = [[event exportString] dataUsingEncoding:NSUTF8StringEncoding];
NSError *writeError = NULL;
[exportHandle writeData:exportData error:&writeError];
if (writeError != NULL) {
NSLog(@"Write error: %@", writeError);
}
}
}];
该代码假定您有一个名为exportFileURL 的方法,该方法返回您要导出数据的位置的NSURL。它还假定您的托管对象有一个名为exportString 的方法,该方法返回您要为对象导出的任何字符串。 createExportFileHandle 方法使用exportFileURL 并且——这很重要——在写入之前寻找文件末尾。类似的东西
- (NSFileHandle *)createExportFileHandle {
NSError *error = NULL;
if (![[NSFileManager defaultManager] fileExistsAtPath:[[self exportFileURL] path]]) {
[[NSFileManager defaultManager] createFileAtPath:[[self exportFileURL] path] contents:nil attributes:nil];
}
NSFileHandle *handle = [NSFileHandle fileHandleForWritingToURL:self.exportFileURL error:&error];
[handle seekToEndOfFile];
return handle;
}
您需要在文件协调器块内创建句柄,因为文件末尾位置不断变化,您希望在开始写入数据之前获取当前位置。
协调文件访问的需要可能会限制您从中获得多少加速。这可能会得到改善。例如,重新编写代码,以便对exportString 的调用位于协调器块之外。将它们全部收集到批处理的一个大字符串中,并协调编写该字符串。不过要注意批处理的字符串不要太大,因为它会在内存中。
请注意,这不会尝试以任何特定顺序放置导出文件。所有对象都被导出,但顺序是不可预测的。由于您没有在问题中使用排序描述符,我猜这并不重要。如果是这样,则异步处理意味着您还有更多工作要做。