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

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

问题描述

我已经能够在我的应用程序中确认这一点,并且我创建了一个快速示例应用程序来确认这一点.设置如下:

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

此设置的灵感来自 WWDC 视频,该视频表明我们可以通过将 masterMOC 设置为私有队列并将其绑定到持久存储来节省后台线程.如果您使用 mainMOC 设置了 NSFetchedResultsController(并且它必须是 mainMOC,因为它是绑定到 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.

一个可能的部分解决方案是将 NSMainQueueConcurrencyType 的额外 NSManagedObjectContext 直接添加到相同的 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)

测试代码显示嵌套上下文中最简单的案例批量获取失败:

#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天全站免登陆