使用父/子上下文时,批量大小不起作用
我已经能够在我的应用程序上确认这一点,并且我创build了一个快速示例应用程序来确认这一点。 这是设置:
您有两个托pipe对象上下文:
masterMOC: NSPrivateQueueConcurrencyType, tied to persistent store coordinator mainMOC: NSMainQueueConcurrencyType, child of masterMOC, NOT tied to any store coordinator
这个设置受到WWDCvideo的启发,这表明我们可以通过将masterMOC
设置为专用队列并将其绑定到持久存储来保存后台线程。 如果您使用mainMOC
设置mainMOC
(并且它必须是与绑定到UI的fetchBatchSize
),并设置fetchBatchSize
,则批处理大小将被忽略,并且所有实体都会同时出现故障。 我启用了SQLitedebugging注释,当滚动成千上万的行(批量大小为20)时,没有任何错误被触发。
如果我做一个简单的调整,即将持久性商店协调员绑定到mainMOC
并使其成为根上下文(也就是说,它不再是master的子级),那么批量大小完美地工作,并且当我滚动数千行,几个故障被解雇。
这是预期的行为? 我错过了什么吗?
您可以在这里下载示例项目
文档中对嵌套上下文的讨论是有限的,它只出现在“iOS v5.0核心数据发行说明”和UIManagedDocument
。 关于获取和嵌套上下文的唯一注释是:
提取和保存操作由父上下文而不是协调器来调解。
鉴于缺乏与嵌套上下文的批量抓取function相关的免责声明,我build议不要求批量抓取和嵌套上下文不兼容。 然而,这似乎是最基本的例子不起作用的情况。 (见下面的testing代码) 。
还有一个开放的雷达提交,描述同样的问题在这里: http ://openradar.appspot.com/11235622,以及其他问题注意到FetchedResultsControllers和嵌套的上下文: 实体的复制,当一个孩子 ManagedObjectContext 所做的更改被推(保存)到其父母 。
一个可能的部分解决scheme可能是将NSMainQueueConcurrencyType
的附加NSManagedObjectContext
直接添加到同一个NSPersistentStoreCoordinator
,唯一的目的是为NSPersistentStoreCoordinator
提供服务。 然后,当用户select项目时,可以将ObjectID传递回嵌套的子上下文,然后可以在嵌套的上下文中执行任何后续编辑。
这显然降低了使用嵌套上下文的好处,并且需要更频繁地保存嵌套上下文和NSFetchedResultsControllers
上下文之间的同步。 然而,取决于应用程序的devise和嵌套上下文相对于批处理的好处,这可能是有用的。 (请参阅下面的示例代码)
testing代码显示嵌套上下文中最简单的情况批次抓取失败:
#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
示例代码显示使用附加上下文来为批处理工作的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. @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 (ie 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
从NSFetchRequest类参考 :
当执行提取操作时,将对整个请求进行评估,并logging所有匹配对象的身份,但是一次只能从持久存储中提取不超过batchSize对象的数据。 从执行请求返回的数组将是一个代理对象,可按需透明地对批处理进行故障排除。
因此,如果托pipe对象上下文没有附加到持久性存储中,则没有任何可取的地方 – 因此,您所看到的行为至less与文档保持一致。 fetchLimit可以在您描述的场景中工作。 我仍然会在bugreporter.apple.com上发布一个雷达。