Iphone 使用父/子上下文时,批大小不起作用
我已经能够在我的应用程序中确认这一点,我创建了一个快速示例应用程序来确认这一点。以下是设置: 您有两个托管对象上下文:Iphone 使用父/子上下文时,批大小不起作用,iphone,objective-c,ios,core-data,Iphone,Objective C,Ios,Core Data,我已经能够在我的应用程序中确认这一点,我创建了一个快速示例应用程序来确认这一点。以下是设置: 您有两个托管对象上下文: masterMOC: NSPrivateQueueConcurrencyType, tied to persistent store coordinator mainMOC: NSMainQueueConcurrencyType, child of masterMOC, NOT tied to any store coordinator 此设置受WWDC视频的启发,该视频建议
masterMOC: NSPrivateQueueConcurrencyType, tied to persistent store coordinator
mainMOC: NSMainQueueConcurrencyType, child of masterMOC, NOT tied to any store coordinator
此设置受WWDC视频的启发,该视频建议我们可以通过将masterMOC
设置为私有队列并将其绑定到持久存储来在后台线程上保存。如果您使用mainMOC
设置NSFetchedResultsController
(必须是mainMOC
,因为它是绑定到UI的),并设置fetchBatchSize
,则批大小将被忽略,所有实体都会立即出错。我启用了SQLite调试注释,当滚动数千行(批大小为20)时,不会触发任何错误
如果我做一个简单的调整,即将持久性存储协调器绑定到mainMOC
,并使其成为根上下文(即,它不再是master的子上下文),那么批大小工作得非常好,当我滚动数千行时,会触发几个错误
这是预期的行为吗?我错过什么了吗
您可以从以下位置下载示例项目:
执行提取时,将评估整个请求并记录所有匹配对象的标识,但每次从持久存储中提取的数据不超过batchSize对象的数据。执行请求返回的数组将是一个代理对象,它会根据需要透明地对批处理进行故障处理
因此,如果托管对象上下文未附加到持久性存储,则没有任何可获取的内容,因此您看到的行为至少在某种程度上与文档一致。fetchLimit可能在您描述的场景中起作用。我仍然会在bugreporter.apple.com上对其进行搜索。文档中对嵌套上下文的讨论有限,它只出现在“iOS v5.0核心数据发行说明”和
UIManagedDocument
中。关于获取和嵌套上下文的唯一注释是:
获取和保存操作由父上下文而不是协调器进行中介
鉴于没有任何与嵌套上下文的批处理获取功能相关的免责声明,我建议不要期望批处理获取和嵌套上下文不兼容。然而,这似乎是最基本的例子不起作用的情况。(参见下面的测试代码)
这里还有一个公开的雷达提交,描述了相同的问题:,以及FetchedResultsController和嵌套上下文中注意到的其他问题:
一种可能的局部解决方案是将另一个NSMainQueueConcurrencyType
的NSManagedObjectContext
直接添加到同一个NSPersistentStoreCoordinator
,其唯一目的是为NSFetchedResultsController
提供服务。当用户选择项时,objectid可以返回到嵌套的子上下文,然后可以在嵌套的上下文中执行任何后续编辑
这显然降低了使用嵌套上下文的好处,并且需要更频繁地保存以在嵌套上下文和nsfetchedResultsController
上下文之间进行同步。然而,根据应用程序的设计以及嵌套上下文与批加载的相对优势,这可能是有用的。(参见下面的示例代码)
测试代码,显示嵌套上下文中最简单案例批量获取失败:
#import "AppDelegate.h"
// Xcode 4.3.3:
// Create a new iOS Master-Detail project called "BatchTest" tick the "Use Core Data" check box.
// Delete all files except the AppDelegate and the BatchTest data model (leave supporting files).
// Delete all properties and methods from AppDelegate.h
// Paste this code into AppDelegate.m
// Switch on core data debugging by editing the "BatchTest" scheme and adding
// -com.apple.CoreData.SQLDebug 1
// To the "arguments passed on launch" list in the "Run" step
// Run.
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
/////////////////////////////////////////////////////////////////////////////////////
// Setup the core data stack.
/////////////////////////////////////////////////////////////////////////////////////
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"BatchTest" withExtension:@"momd"];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSURL *appDocsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [appDocsDirectory URLByAppendingPathComponent:@"BatchTest.sqlite"];
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:nil];
NSManagedObjectContext *parentContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
parentContext.persistentStoreCoordinator = coordinator;
NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
childContext.parentContext = parentContext;
/////////////////////////////////////////////////////////////////////////////////////
// Load some test data and reset the context.
/////////////////////////////////////////////////////////////////////////////////////
[parentContext performBlockAndWait:^{
for (int i=0; i<1000; i++) {
[NSEntityDescription insertNewObjectForEntityForName:@"Event" inManagedObjectContext:parentContext];
}
[parentContext save:nil];
[parentContext reset];
}];
/////////////////////////////////////////////////////////////////////////////////////
// Test Batched Fetching
/////////////////////////////////////////////////////////////////////////////////////
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Event"];
request.fetchBatchSize = 10;
// Fetch from the child.
NSArray *results = [childContext executeFetchRequest:request error:nil];
NSLog(@"Object 500: %@", [results objectAtIndex:500]);
// Result is all 1000 rows fetched in full, no subsequent batch fetching for event 500.
[childContext reset];
[parentContext performBlockAndWait:^{
[parentContext reset];
// Fetch from the parent.
NSArray *results = [parentContext executeFetchRequest:request error:nil];
NSLog(@"Object 500: %@", [results objectAtIndex:500]);
// Result is 1000 primary keys fetched, followed by a batch of 10 rows to find event 500.
}];
return YES;
}
@end
#import "AppDelegate.h"
// Xcode 4.3.3:
// Create a new iOS Master-Detail project called "BatchTest" tick the "Use Core Data" check box.
// Delete all files except the AppDelegate and the BatchTest data model (leave supporting files).
// Delete all properties and methods from AppDelegate.h
// Paste this code into AppDelegate.m
// Switch on core data debugging by editing the "BatchTest" scheme and adding
// -com.apple.CoreData.SQLDebug 1
// To the "arguments passed on launch" list in the "Run" step
// Run.
@interface AppDelegate () {
NSManagedObjectContext *backgroundContext;
NSManagedObjectContext *editingContext;
NSManagedObjectContext *fetchedResultsControllerContext;
NSManagedObject *selectedObject;
}
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
/////////////////////////////////////////////////////////////////////////////////////
// Setup the core data stack.
/////////////////////////////////////////////////////////////////////////////////////
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"BatchTest" withExtension:@"momd"];
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSURL *appDocsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [appDocsDirectory URLByAppendingPathComponent:@"BatchTest.sqlite"];
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
[coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:nil];
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
backgroundContext.persistentStoreCoordinator = coordinator;
editingContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
editingContext.parentContext = backgroundContext;
fetchedResultsControllerContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
fetchedResultsControllerContext.persistentStoreCoordinator = coordinator;
/////////////////////////////////////////////////////////////////////////////////////
// Load some test data and reset the context.
/////////////////////////////////////////////////////////////////////////////////////
[backgroundContext performBlockAndWait:^{
for (int i=0; i<1000; i++) {
[NSEntityDescription insertNewObjectForEntityForName:@"Event" inManagedObjectContext:backgroundContext];
}
[backgroundContext save:nil];
[backgroundContext reset];
}];
/////////////////////////////////////////////////////////////////////////////////////
// Example of three contexts performing different roles.
/////////////////////////////////////////////////////////////////////////////////////
// The fetchedResultsControllerContext will batch correctly as it is tied directly
// to the persistent store. It can be used to drive the UI as it is a Main Queue context.
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Event"];
request.fetchBatchSize = 10;
NSArray *fetchResults = [fetchedResultsControllerContext executeFetchRequest:request error:nil];
// User selects an object in the fetchedResultsControllerContext (i.e. in a UITableView).
selectedObject = [fetchResults lastObject];
NSLog(@"**** selectedObject.timeStamp before editing:%@", [selectedObject valueForKey:@"timeStamp"]);
// Pass the object to the editing context for editing using its objectID.
NSManagedObjectID *selectedObjectID = selectedObject.objectID;
NSManagedObject *objectForEditing = [editingContext objectWithID:selectedObjectID];
// Edit the object
[objectForEditing setValue:[NSDate date] forKey:@"timeStamp"];
// Subscribe to save notifications of the background context so the
// fetchedResultsControllerContext will be updated after the background save occurs.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(backgroundContextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:backgroundContext];
// Save the editing context to push changes up to the parent, then background save.
[editingContext save:nil];
[backgroundContext performBlock:^{
[backgroundContext save:nil];
}];
return YES;
}
- (void)backgroundContextDidSave:(NSNotification *)notification {
[fetchedResultsControllerContext mergeChangesFromContextDidSaveNotification:notification];
NSLog(@"**** selectedObject.timeStamp after editing:%@", [selectedObject valueForKey:@"timeStamp"]);
// Merging changes into the fetchedResultsControllerContext would trigger updates
// to an NSFetchedResultsController and it's UITableView where these set up.
}
@end
我在同样使用嵌套上下文的UIManagedDocument中也遇到过同样的问题:。我也曾在苹果开发者论坛上询问过,但没有得到任何回应。是的,但是我喜欢将持久存储连接到私有队列上下文的功能是在后台保存,这听起来很棒。关于如何恢复该功能,您有什么想法吗?因此,我们建议对大多数内容保留现有设置,但只使用附加上下文来服务于
NSFetchedResultsController
,即只读。您仍然可以在嵌套上下文中编辑对象并将其保存在背景中。我假设asNSFetchedResultsController
主要用于填充UITableViews
,当表格视图可见时,您不进行编辑,因此可以将所选对象传递回嵌套上下文(使用它的objectID
)进行编辑,然后在保存时通知已获取的结果控制器上下文(背景)。最糟糕的情况是,在保存运行时,在表视图中出现任何更改之前,您可能会看到延迟。但如果这是一个问题,您可能可以将任何长时间运行的保存管理到不同的点。它更紧凑,但批处理已中断,因此除了等待Apple的修复之外,我看不到其他选择。等等,您可以设置相同的值是否将存储协调器发送到多个上下文?是的。如果您想进行实验,上面的代码应该很容易运行。