3

マルチスレッドのCoreData対応アプリで問題が発生しているので、自分が何をどのように行っているかをしっかりと確認する必要があると考えました。次のことがうまくいくかどうか教えてください。

CoreDataのものを処理するシングルトンDataManagerクラスがあります。managedObjectContextスレッドごとに異なるMOCを返すプロパティがあります。したがって、NSMutableDictionary *_threadContextDict(コンテキストへの文字列スレッド名)とNSMutableDictionary *_threadDict(スレッドへの文字列スレッド名)が与えられると、次のようになります。

-(NSManagedObjectContext *)managedObjectContext
{
  if ([NSThread currentThread] == [NSThread mainThread])
  {
    MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
    return delegate.managedObjectContext; //MOC created in delegate code on main thread
  }
  else
  {
    NSString *thisThread = [[NSThread currentThread] description];
    {
      if ([_threadContextDict objectForKey:thisThread] != nil)
      {
        return [_threadContextDict objectForKey:thisThread];
      }
      else
      {
        NSManagedObjectContext *context = [[NSManagedObjectContext alloc]init];
        MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
        [context setPersistentStoreCoordinator:delegate.persistentStoreCoordinator];
        [_threadContextDict setObject:context forKey:thisThread];
        [_threadDict setObject:[NSThread currentThread] forKey:thisThread];

        //merge changes notifications
        NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
        [center addObserver:self selector:@selector(mergeChanges:)
            name:NSManagedObjectContextDidSaveNotification object:context];

        return context;
      }
    }
  }
}

このmergeChanges方法では、着信通知からの変更を、通知を生成したコンテキストを除くすべてのコンテキストにマージします。次のようになります。

-(void)mergeChanges:(NSNotification *)notification
{
  MyAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
  NSManagedObjectContext *context = delegate.managedObjectContext;
  [context performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification)
     withObject:notification waitUntilDone:YES];

  for (NSString *element in [_threadContextDict allKeys])
  {
    if (![element isEqualToString:[[NSThread currentThread] description]])
    {
      NSThread *thread = [_threadDict objectForKey:element];
      NSManagedObjectContext *threadContext = [_threadContextDict objectForKey:element];
      [threadContext performSelector:@selector(mergeChangesFromContextDidSaveNotification)
         onThread:thread withObject:notification waitUntilDone:YES];
    }
  }
}

MOCに変更を保存するときはいつでも、前述のプロパティから取得したコンテキストを呼び出す、saveContextこの共有のメソッドの呼び出しで行われます。DataManagersave

-(void)saveContext
{
  NSManagedObjectContext *context = self.managedObjectContext;
  NSError *err = nil;
  [context save:&err];
  //report error if necessary, etc.
}

Core Dataマルチスレッドルールについての私の理解を考えると、これはうまくいくはずだと思います。スレッドごとに個別のコンテキストを使用していますが、すべてのスレッドに同じ永続ストアがあります。しかし、これを使用すると、スレッドが同じオブジェクト(NSManagedObjectサブクラス)で機能していなくても、多くのマージの競合が発生します。ネットワークからデータをダウンロードし、結果を解析してCoreDataに保存しているだけです。

私は何か間違ったことをしていますか?インスタンスを使用していくつかのことをロックしようとしましNSLockたが、ハングアップします。

更新/解決策:1つの簡単なことを追加することで、この作業を行うことができました。それは、終了時に辞書からスレッド/MOCペアを削除する方法です。dispatch_asyncCore Dataを実行する各呼び出しの各ブロックの最後で、を呼び出します[self removeThread]。これにより、現在のスレッドとそのMOCがディクショナリから削除されます。また、変更をメインスレッドMOCにマージするだけです。事実上、これは、バックグラウンドスレッドで作業するたびに、新しいMOCを取得することを意味します。

userInfoDictまた、を呼び出す代わりに、に番号を追加することでスレッドを区別しdescriptionます。番号は、呼び出されるたびにより高い番号を返すクラスの読み取り専用プロパティによって取得されます。

4

1 に答える 1

7

敬意を払って、あなたのアプローチは悪夢であり、問​​題がある場合は何かを解決するためにそれをデバッグすることはさらに悪いはずです。最初の問題はこれです:

シングルトンDataManagerを持っています

異なるスレッド上の異なるエンティティでコアデータ操作を管理するシングルトンオブジェクトはありません。シングルトンは、特にマルチスレッド環境では扱いが難しく、コアデータで使用するにはさらに悪いアプローチです。

次に、マルチスレッドで作業するためにNSThreadを使用しないでください。より最近のAPIがあります。グランドセントラルディスパッチまたはNSOperation/NSOperationQueueを使用します。Appleは、ブロック(iOS 4)の導入以来、NSThreadからの移行を人々に奨励してきました。また、将来の参考のために、オブジェクトの説明を使用している方法で使用しないでください。説明は通常/ほとんどデバッグ目的で使用されます。そこにある情報を比較に使用するべきではありません。ポインタ値すらありません(そのため、==の代わりにisEqualを使用する必要があります)。

これは、コアデータとマルチスレッドについて知っておく必要があることです。

  1. スレッドごとに1つのコンテキストを作成します。コアデータテンプレートは、すでにメインスレッドコンテキストを作成しています。バックグラウンドスレッドの実行の開始時に(ブロック内、またはNSOperationサブクラスのメインメソッド上で)、コンテキストを初期化します。
  2. コンテキストが初期化され、適切なpersistentStoreCoordinatorができたら、NSManagedObjectContextObjectsDidChangeNotificationをリッスンします。通知をリッスンしているオブジェクトは、コンテキストが保存されていたのと同じスレッドで通知を受け取ります。これはメインスレッドとは異なるため、受信コンテキストが使用されているスレッドでマージコンテキストを使用してマージ呼び出しを実行します。メインスレッドとは異なるスレッド内でコンテキストを使用していて、メインスレッドとマージする場合は、メインスレッド内でmergeメソッドを呼び出す必要があるとします。あなたはdispatch_async(dispatch_get_main_queue()、^ {//ここにコード});でそれを行うことができます。
  3. managedObjectContextが存在するスレッドの外部でNSManagedObjectを使用しないでください。

これらおよびその他の単純なルールを使用すると、マルチスレッド環境でのコアデータの管理が容易になります。あなたのアプローチは実装がより難しく、デバッグがより悪いです。アーキテクチャにいくつかの変更を加えます。作業しているスレッドに応じて(集中化ではなく)コンテキストを管理します。コンテキストへの参照をスコープ外に保持しないでください。最初のコンテキストが作成されたら、スレッドにコンテキストを作成するのに費用はかかりません。同じブロック/NSOperation実行内にある限り、同じコンテキストを再利用できます。

于 2012-09-17T21:20:32.270 に答える