使用父/子上下文时,批大小不起作用 [英] Batch size does not work when using parent/child contexts

查看:135
本文介绍了使用父/子上下文时,批大小不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经能够在我的申请表上确认这一点,并且我创建了一个快速示例应用程序来确认这一点。这是设置:

I've been able to confirm this on my application, and a quick sample application I created to confirm this. Here's the setup:

您有两个管理对象上下文:

You have two managed object contexts:

masterMOC: NSPrivateQueueConcurrencyType, tied to persistent store coordinator
mainMOC: NSMainQueueConcurrencyType, child of masterMOC, NOT tied to any store coordinator


b $ b

这个设置的灵感来自WWDC视频,这表明我们可以通过将 masterMOC 设置为私有队列并将其绑定到后台线程持久存储。如果使用 mainMOC (并且它必须是 mainMOC )设置 NSFetchedResultsController code>,因为这是一个绑定到UI),并设置 fetchBatchSize ,批量大小被忽略,所有实体一次故障。我启用了SQLite调试注释,当滚动数千行(批量大小为20)时,没有任何故障被触发。

This setup was inspired from the WWDC video, which suggests we can save on a background thread by setting the masterMOC to a private queue and tie it to the persistent store. If you set up an NSFetchedResultsController using the mainMOC (and it must be the mainMOC since that's the one tied to the UI), and set a fetchBatchSize, the batch size is disregarded and all entities are faulted in at once. I enabled the SQLite debug annotations, and when scrolling through thousands of rows (with a batch size of 20), no faults are fired what so ever.

如果我做一个简单的调整,即将持久性存储协调器绑定到 mainMOC 并使其成为根上下文(即它不再是master的子节点),那么批处理大小完全,当我滚动数千行时,会引发几个故障。

If I make one simple adjustment, namely tie the persistent store coordinator to the mainMOC and make it a root context (that is, it is no longer a child of master), then the batch size works perfectly, and as I scroll through thousands of rows, several faults are fired.

这是预期的行为吗?我缺少某些东西?

Is this expected behavior? Am I missing something?

您可以下载示例项目此处

推荐答案

文档中嵌套上下文的讨论有限,在iOS v5.0的核心数据发行说明中和在 UIManagedDocument 中。对提取和嵌套上下文的唯一注释是:

There is limited discussion of nested contexts in the documentation, it only appears in "Core Data Release Notes for iOS v5.0", and in UIManagedDocument. The only comment on fetching and nested contexts is:


提取和保存操作由父上下文而不是协调器介导。

fetch and save operations are mediated by the parent context instead of a coordinator.

由于缺少与嵌套上下文的批量提取功能相关的任何免责声明,我建议不要期望批量提取和嵌套上下文是不兼容的。但是这似乎是最基本的例子不起作用的情况。

Given the lack of any disclaimers relating to the functionality of batch fetching with nested contexts, I would suggest it is not expected that batch fetching and nested contexts are incompatible. However this seems to be the case as the most basic example does not function. (See test code below).

还有一个打开的雷达提交,描述同样的问题: http://openradar.appspot.com/11235622 ,以及使用FetchedResultsControllers和嵌套上下文记录的其他问题:当子项ManagedObjectContext发生更改时,重复实体将其推送(保存)到其父级

There is also an open radar submission describing the same problem here: http://openradar.appspot.com/11235622, and other problems noted with FetchedResultsControllers and nested contexts: Duplication of entity when change made by a child ManagedObjectContext is pushed (saved) to its parent.

可能的部分解决方案可能是添加 NSManagedObjectContext NSMainQueueConcurrencyType 直接到相同的 NSPersistentStoreCoordinator ,仅用于提供 NSFetchedResultsController 。然后,当用户选择项目时,ObjectID可以被切换回嵌套的子上下文,然后可以在嵌套的上下文中执行任何后续编辑。

A possible partial solution could be to add an additional NSManagedObjectContext of NSMainQueueConcurrencyType directly to the same NSPersistentStoreCoordinator for the sole purpose of serving the NSFetchedResultsController. ObjectIDs could then be handed back to the nested child context when the user selects items, and any subsequent editing can then be performed in the nested contexts.

使用嵌套上下文,并且需要更频繁地保存以在嵌套上下文和 NSFetchedResultsControllers 上下文之间同步。然而,根据应用程序的设计和嵌套上下文与批处理加载的相对好处,这可能是有用的。 (请参阅下面的示例代码)

This obviously reduces the benefit of using nested contexts and would require more frequent saving to synchronise between the nested contexts and the NSFetchedResultsControllers context. However depending on the design of the application and the relative benefits of nested contexts vs batch loading this may be useful. (See example code below)

测试代码显示嵌套上下文中最简单的批量抓取失败 p>

Test code showing failure of simplest case batch fetching in nested contexts:

#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 服务:

Example code showing use of an additional context to serve an NSFetchedResultsController with batching working:

#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

这篇关于使用父/子上下文时,批大小不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆