3

アプリでコアデータを使用し、次の 3 つのコンテキストを使用します。

  • __masterManagedObjectContext ->は、NSPersistentStoreCoordinator を持ち、データをディスクに保存するコンテキストです。

  • _mainManagedObjectContext ->は、どこでもアプリによって使用されるコンテキストです

  • dispatchContext ->バックグラウンド メソッドで使用されるコンテキスト。Web サービスへのアクセスとすべてのコアデータの挿入/更新があります。

私のソリューションを実現するためにいくつかのコードを入れます:

アプリの初期化コード:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions //a app começa aqui
{        
    NSPersistentStoreCoordinator *coordinator = [self newPersistentStoreCoordinator];
    __masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    [__masterManagedObjectContext setPersistentStoreCoordinator:coordinator];

    _mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [_mainManagedObjectContext setUndoManager:nil];
    [_mainManagedObjectContext setParentContext:__masterManagedObjectContext];

    return YES;
}

ストアコーディネーターの新規作成方法

- (NSPersistentStoreCoordinator *)newPersistentStoreCoordinator
{
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"example.sqlite"];

    NSError *error = nil;
    NSPersistentStoreCoordinator *pC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self newManagedObjectModel]];
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
    if (![pC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
    {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    return pC;
}

- (NSManagedObjectModel *)newManagedObjectModel
{
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"example" withExtension:@"momd"];
    NSManagedObjectModel *newManagedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return newManagedObjectModel;

}

コンテキスト指定によるスレッド呼び出し (必須コード):

@try
{
    dispatchContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
    [dispatchContext setUndoManager:nil];
    [dispatchContext setParentContext:__masterManagedObjectContext];

    NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
    [notify addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:dispatchContext];

    if(dispatchContext !=  nil)
    {
        [NSThread detachNewThreadSelector:@selector(parseDataWithObjects) toTarget:self withObject:nil];
    }
    else
    {
        NSLog(@"context IS NIL");
    }
}

バックグラウンド メソッド:

- (void)parseDataWithObjects
{
    [dispatchContext lock];

    ...
    webservice data parse, and core data inserting/updating (+/- 5MB)
    ...

    [dispatchContext save:&error];
    [dispatchContext unlock];
    [__masterManagedObjectContext save:nil];
}

このメソッドは、coredata データにアクセスするためにすべての UI で呼び出されます。

- (NSManagedObjectContext *)managedObjectContext
{
    return _mainManagedObjectContext;
}

呼び出し例:

NSManagedObjectContext *context =  [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];

...fetching, update, ...

今、私の本当に問題:

マスター コンテキストを保存する必要がある場合 ( [__masterManagedObjectContext save:nil];、バックグラウンドで)、メイン コンテキスト ( ) にアクセスしようとすると_mainManagedObjectContext、アプリがフリーズします (おそらくロック?)。

保存処理には時間がかかります (大量のデータ (約 6 MB) のため)。保存中にアプリが遅くなり、このプロセスの実行中に一部のデータにアクセスすると、アプリが永久にフリーズします (強制終了する必要があります)。

別の問題は、コンテキストをマージすることです。他のviewControllerでメインコンテキストを使用し、そのコンテキストを保存すると、アプリを閉じるまですべてが正常に機能すると想像してください。アプリを再度開くと、何も保存されませんでした。

私は何を間違っていますか?今まで、この文脈は私を混乱させていました。誰かが私を助けることができますか?本当に感謝しております :)

- - - - - 編集:

Florian Kugler の回答に続いて、現在は 2 つのコンテキストしかなく、それぞれが同じコーディネーターを持っています。

アプリが初期化されると、次のメソッドを呼び出します。

-(void) createContexts
{
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"example" withExtension:@"momd"];
    NSManagedObjectModel *newManagedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"example.sqlite"];
    NSError *error = nil;
    pC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:newManagedObjectModel];
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
    if (![pC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
    {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    mainManagedObjectContext.persistentStoreCoordinator = pC;

}

- (void)mergeChanges:(NSNotification*)notification {
    [mainManagedObjectContext performBlock:^{
        [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    }];
}

- (void)saveMasterContext
{
    [mainManagedObjectContext performBlock:^{
        [mainManagedObjectContext save:nil];
    }];
}

データのインポートを (バックグラウンドで) 開始するには、次のコードを使用します。

NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:backgroundContext];

[NSThread detachNewThreadSelector:@selector(parseDataWithObjects) toTarget:self withObject:nil];

私の背景方法:

- (void)parseDataWithObjects
{
    [self resetTime];
    backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    backgroundContext.persistentStoreCoordinator = pC;

    ...

    [backgroundContext save:&error];
}

再開しています...

  • 1st - メインコンテキストを作成します
  • 2 番目 - 保存後に変更をマージするために、バックグラウンド コンテキスト通知を定義します。
  • 3番目 - バックグラウンド メソッドを呼び出します
  • 4rd - バックグラウンド コンテキストを保存します

そして、パフォーマンスは本当に優れています。しかし、アプリが少しフリーズします。「mergeChanges」にあると思います。私は何か間違ったことをしていますか?

4

1 に答える 1

8

NSPrivateQueueConcurrencyTypeorを使用するときNSMainQueueConcurrencyTypeは、これらのコンテキストで行うすべてを でラップする必要がありますperformBlock:。これにより、これらのコマンドが正しいキューで実行されるようになります。たとえば、マスター コンテキストを保存すると、次のようになります。

[__masterManagedObjectContext performBlock:^{
    [__masterManagedObjectContext save];
}];

さらに、ディスパッチ コンテキストをマスター コンテキストの子として設定した場合、ディスパッチ コンテキストから手動で変更をマージする必要はありません。子コンテキストを保存するとすぐに、変更が親コンテキストにプッシュされます。

もう 1 つの問題は、あるスレッドで NSConfinementConcurrencyType を使用してコンテキストを初期化し、それを別のスレッドで使用することです。この同時実行タイプでは、使用するスレッドでコンテキストを初期化することが非常に重要です。

ただし、NSConfinementConcurrencyType をまったく使用しないことをお勧めします。考えられる代替手段の 1 つは、次のようにディスパッチ コンテキストをセットアップすることNSPrivateQueueConcurrencyTypeです。

NSManagedObjectContext* dispatchContext = [[NSManagedObjectContext] alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
dispatchContext.parentContext = __masterManagedObjectContext;

[dispatchContext performBlock:^{
    [self parseDataWithObjects];
}];

これを行う場合、ロックを取得する必要はありません。ブロック内で行うことはすべて、プライベート シリアル キューで処理されます。

パフォーマンス

保存にかなり時間がかかり、アプリが応答しなくなることは既に述べました。

ディスパッチ コンテキストに大量のデータをインポートする予定がある場合、3 つのネストされたコンテキスト (プライベート <- メイン <- ディスパッチ) を使用した管理対象オブジェクト コンテキストのセットアップは最適な選択ではありません。ディスパッチコンテキストで行ったすべての変更は、「ルート」コンテキストに保存する前にメインコンテキストにコピーする必要があるため、これにより常にメインスレッドがかなりの時間ブロックされます。

私は最近、これに関する記事を書き、さまざまなコア データ設定のパフォーマンスを比較しましたフォローアップの投稿で、メイン スレッドでこのセットアップに時間がかかる理由を詳しく説明します。

大量のデータをインポートするには、独立した管理対象オブジェクト コンテキストを共通の永続ストア コーディネーターと共に使用する方がはるかに高速です。NSMainQueueConcurrencyType1 つのコンテキスト(すべての UI 関連のものに使用) と別のコンテキスト(NSPrivateQueueConcurrencyTypeデータのインポート用) を作成します。両方に同じ永続ストア コーディネーターを割り当てます。

// assuming you have persistendStoreCoordinator

NSManagedObjectContext* mainContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
mainContext.persistentStoreCoordinator = persistentStoreCoordinator;

NSManagedObjectContext* backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
backgroundContext.persistentStoreCoordinator = persistentStoreCoordinator;

この設定では、ネストされたコンテキストで得られる自動変更伝播は提供されませんが、保存通知を介して非常に簡単に行うことができます。performBlock:マージが正しいスレッドで行われるように、再び を使用していることに注意してください。

// ...
NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];
[notify addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:backgroundContext];
// ...

- (void)mergeChanges:(NSNotification*)notification {
    [mainContext performBlock:^{
        [mainContext mergeChangesFromContextDidSaveNotification:notification];
    }];
}

お役に立てれば!

于 2013-05-22T13:21:58.633 に答える