10

私は自分のアプリケーションでこれを確認することができました。これを確認するために作成した簡単なサンプルアプリケーションです。設定は次のとおりです。

2つの管理対象オブジェクトコンテキストがあります。

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

このセットアップは、WWDCビデオから着想を得たものでmasterMOC、プライベートキューに設定し、永続ストアに関連付けることで、バックグラウンドスレッドを節約できることを示しています。NSFetchedResultsControllerを使用して設定し( UIに関連付けられているためであるmainMOC必要があります)、を設定すると、バッチサイズは無視され、すべてのエンティティが一度にフォールトインされます。SQLiteデバッグアノテーションを有効にしました。数千行(バッチサイズ20)をスクロールしても、障害は発生しません。mainMOCfetchBatchSize

簡単な調整を1つ行うと、つまり、永続ストアコーディネーターをに結び付けてmainMOCルートコンテキストにする(つまり、マスターの子ではなくなる)と、バッチサイズは完全に機能し、数千行をスクロールします。 、いくつかの障害が発生します。

これは予想される動作ですか?私は何かが足りないのですか?

ここからサンプルプロジェクトをダウンロードできます

4

2 に答える 2

6

ドキュメント内のネストされたコンテキストの説明は限られており、「iOSv5.0のCoreDataリリースノート」とにのみ表示されUIManagedDocumentます。コンテキストのフェッチとネストに関する唯一のコメントは次のとおりです。

フェッチおよび保存操作は、コーディネーターではなく親コンテキストによって仲介されます。

ネストされたコンテキストでのバッチフェッチの機能に関する免責事項がないことを考えると、バッチフェッチとネストされたコンテキストに互換性がないことは予想されないことをお勧めします。ただし、最も基本的な例が機能しないため、これが当てはまるようです。(以下のテストコードを参照してください)

ここに同じ問題を説明するオープンレーダーの提出もあります:http://openradar.appspot.com/11235622、およびFetchedResultsControllersとネストされたコンテキストで指摘された他の問題:子のManagedObjectContextによって行われた変更がプッシュ(保存)されたときのエンティティの複製その親に

考えられる部分的な解決策は、を提供するという唯一の目的のために、同じものNSManagedObjectContextNSMainQueueConcurrencyType直接の追加を追加することです。その後、ユーザーがアイテムを選択したときにObjectIDをネストされた子コンテキストに戻すことができ、その後の編集はネストされたコンテキストで実行できます。NSPersistentStoreCoordinatorNSFetchedResultsController

これにより、ネストされたコンテキストを使用するメリットが明らかに減少し、ネストされたコンテキストとコンテキストの間で同期するために、より頻繁に保存する必要がありNSFetchedResultsControllersます。ただし、アプリケーションの設計と、ネストされたコンテキストとバッチロードの相対的な利点によっては、これが役立つ場合があります。(以下のサンプルコードを参照してください)

ネストされたコンテキストでの最も単純なケースのバッチフェッチの失敗を示すテストコード:

#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 (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
于 2012-07-13T12:30:26.573 に答える
0

NSFetchRequestクラスリファレンスから:

フェッチが実行されると、リクエスト全体が評価され、一致するすべてのオブジェクトのIDが記録されますが、一度に永続ストアからフェッチされるのは、batchSizeオブジェクトのデータのみです。リクエストの実行から返される配列は、オンデマンドでバッチを透過的にフォールトするプロキシオブジェクトになります。

したがって、管理対象オブジェクトのコンテキストが永続ストアにアタッチされていない場合、フェッチするものは何もありません。したがって、表示される動作は、ドキュメントと少なくともある程度一致しています。fetchLimitは、説明しているシナリオで機能する場合があります。私はまだbugreporter.apple.comでレーダーを発射します。

于 2012-07-12T00:41:17.707 に答える