2

典型的な設定: を持つメイン スレッドとmainMOC、独自の を持つバックグラウンド スレッドがありますbackgroundMOCbackgroundMOCバックグラウンド スレッドは、 ブロックを にディスパッチすることにより、 に対して読み取り専用操作を実行しますbackgroundQueue

からのbackgroundMOC変更をマージする必要があるmainMOCため、登録してから次のNSManagedObjectContextDidSaveNotificationようなことを行います

- (void)mainMocDidSave:(NSNotification *)notification {
    dispatch_async(backgroundQueue, ^{
        [backgroundMoc mergeChangesFromContextDidSaveNotification:notification];
    });
}

ユーザーが 内のオブジェクトを削除するとしますmainMOC。マージは将来のある時点で行われるため、上記のコードは安全ではないように思えます。backgroundQueueマージが完了するまで、削除されたオブジェクトを使用しようとしているブロックが に残っている可能性があります。

明らかな解決策は、dispatch_sync代わりに (またはperformBlockAndWait, performSelector:OnThread:...) を代わりに使用することです。インターウェブで見たコード スニペットから、これは誰もが行っていることのようです。しかし、私はこの解決策にも満足していません。

この名前NSManagedObjectContextDidSaveNotificationは、通知が配信されたときに保存が既に行われていることを意味します。したがって、対応する行は基礎となるデータベースからすでに削除されています (sqlite ストアを想定)。dispatch_sync変更をマージする前に、キューの他のブロックが終了するのを待つ必要があり、これらの他のブロックは削除されたオブジェクトを引き続き処理しようとする可能性があり、NSObjectInaccessibleException.

あるスレッド/キューから別のスレッド/キューへの変更をマージする正しい方法は、

  1. バックグラウンド スレッドにNSManagedObjectContextWillSaveNotificationサブスクライブします。NSManagedObjectContextDidSaveNotification
  2. On NSManagedObjectContextWillSaveNotification: を空にし、backgroundQueue新しいブロックをキューにディスパッチするすべての操作を一時停止します。
  3. On NSManagedObjectContextDidSaveNotification: 変更を同期的にマージします。
  4. バックグラウンド キューで通常の操作を再開します。

これは正しいアプローチですか、何か不足していますか?

4

1 に答える 1

0

あなたと同様の問題を経験した2つのプロジェクトで、次の構造を使用しています。まず、シングルトン サービスを使用して、バックグラウンド スレッドのマージと変更の読み取りが 1 つだけになるようにします。

AppDelegate.m

- (NSManagedObjectContext *)managedObjectContext {
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        // It is crucial to use the correct concurrency type!
        _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _managedObjectContext;
}

- (void)saveContext {
    NSError *error = nil;
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil) {
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
             // Replace this implementation with code to handle the error appropriately.
             // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
        else {
            [[NSNotificationCenter defaultCenter] postNotificationName:@"ParentContextDidSaveNotification" object:nil];
        }
    }
}

BackgroundService.m

- (id)init {
    self = [super init];

    if (self) {
        [self managedObjectContext];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(parentContextDidSave) name:@"ParentContextDidSaveNotification" object:nil];
    }

    return self;
}

- (NSManagedObjectContext *)managedObjectContext {
    if (!_managedObjectContext) {
        // Again, make sure you use the correct concurrency type!
        _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [_managedObjectContext setParentContext:[(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext]];
    }

    return _managedObjectContext;
}


- (BOOL)saveContext {
    @synchronized(self) {
        BOOL successful = YES;

        // Bad practice, process errors appropriately.
        [[self managedObjectContext] save:nil];

        [[[self managedObjectContext] parentContext] performBlock:^{
            [(AppDelegate *)[[UIApplication sharedApplication] delegate] saveContext];
        }];

        return successful;
    }
}

- (void)parentContextDidSave {
    [[self managedObjectContext] reset];

    [[NSNotificationCenter defaultCenter] postNotificationName:@"ManagedObjectContextResetNotification" object:nil];
}
于 2013-07-09T12:29:15.450 に答える