2012-10-02 11 views
6

私が一言で言いたいのは、WebサービスからCore Data Sqlite3データベースにプルされたJSONオブジェクトをバックグラウンドキューに保存することです。保存はGCDを介して作成したシリアライズされたバックグラウンドキューで行われ、バックグラウンドキュー用に作成されたNSManagedObjectContextのセカンダリインスタンスに保存されます。保存が完了したら、メインスレッド上のNSManagedObjectContextのインスタンスを新しく作成/更新したオブジェクトで更新する必要があります。主なスレッド上のNSManagedObjectContextのインスタンスは、バックグラウンドコンテキストに保存されたオブジェクトを見つけることができません。以下は、コードサンプルで実行しているアクションのリストです。私が間違ってやっていることに関する考えは?バックグラウンド問題のコアデータ保存オブジェクト

  • 、GCDを経由してバックグラウンドのキューを作成し、すべての前処理ロジックを実行し、そのスレッド上でバックグラウンド・コンテキストを保存:

// process in the background queue 
dispatch_async(backgroundQueue, ^(void){ 

    if (savedObjectIDs.count > 0) { 
     [savedObjectIDs removeAllObjects]; 
    } 
    if (savedObjectClass) { 
     savedObjectClass = nil; 
    } 

    // set the thead name 
    NSThread *currentThread = [NSThread currentThread]; 
    [currentThread setName:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME]; 

    // if there is not already a background context, then create one 
    if (!_backgroundQueueManagedObjectContext) { 
     NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 
     if (coordinator != nil) { 
      _backgroundQueueManagedObjectContext = [[NSManagedObjectContext alloc] init]; 
      [_backgroundQueueManagedObjectContext setPersistentStoreCoordinator:coordinator]; 
     } 
    } 

    // save the JSON dictionary starting at the upper most level of the key path, and return all created/updated objects in an array 
    NSArray *objectIds = [self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundQueueManagedObjectContext level:0]; 

    // save the object IDs and the completion block to global variables so we can access them after the save 
    if (objectIds) { 
     [savedObjectIDs addObjectsFromArray:objectIds]; 
    } 
    if (completion) { 
     saveCompletionBlock = completion; 
    } 
    if (managedObjectClass) { 
     savedObjectClass = managedObjectClass; 
    } 

    // save all changes object context 
    [self saveManagedObjectContext]; 
}); 
  • 「saveManagedObjectContext」方法は、基本的には、スレッドが実行されているを見て、適切なコンテキストを保存します。このメソッドが正しく機能していることを確認しましたので、ここにコードを配置しません。

  • このコードのすべては、シングルトンに常駐し、シングルトンのinitメソッドでは、私は「NSManagedObjectContextDidSaveNotification」のリスナーを追加してい、それはmergeChangesFromContextDidSaveNotification:メソッドを呼び出し

// merge changes from the context did save notification to the main context 
- (void)mergeChangesFromContextDidSaveNotification:(NSNotification *)notification 
{ 
    NSThread *currentThread = [NSThread currentThread]; 

    if ([currentThread.name isEqual:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME]) { 

     // merge changes to the primary context, and wait for the action to complete on the main thread 
     [_managedObjectContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES]; 

     // on the main thread fetch all new data and call the completion block 
     dispatch_async(dispatch_get_main_queue(), ^{ 

      // get objects from the database 
      NSMutableArray *objects = [[NSMutableArray alloc] init]; 
      for (id objectID in savedObjectIDs) { 
       NSError *error; 
       id object = [_managedObjectContext existingObjectWithID:objectID error:&error]; 
       if (error) { 
        [self logError:error]; 
       } else if (object) { 
        [objects addObject:object]; 
       } 
      } 

      // remove all saved object IDs from the array 
      [savedObjectIDs removeAllObjects]; 
      savedObjectClass = nil; 

      // call the completion block 
      //completion(objects); 
      saveCompletionBlock(objects); 

      // clear the saved completion block 
      saveCompletionBlock = nil; 
     }); 
    } 
} 

私は「mergeChangesFromContextDidSaveNotification:」呼び出しています上記のあなたが方法で見ることができるようにメインスレッド上を、そして私が行っまで待機するアクションを設定しています。リンゴの文書によれば、バックグラウンドスレッドは、その呼び出しがその呼び出しの下のコードの残りの部分を続ける前に、そのアクションが完了するまで待つべきです。上で述べたように、このコードを実行するとすべて動作するように見えますが、フェッチされたオブジェクトをコンソールに出力しようとすると、何も戻ってこないのです。マージは実際には起こっていないか、残りのコードが実行される前に終了していない可能性があります。マージが完了したことを確認するためにリッスンする必要があるという別の通知がありますか?または、マージの後で、主オブジェクトのコンテキストを保存する必要がありますか?

また、悪いコードの書式設定についてお詫び申し上げますが、SOのコードタグはメソッド定義を好まないようです。

ありがとうございました!

UPDATE:

私は、以下の推奨された変更を行ったが、それでも同じ問題を抱えてきました。以下は私が持っている更新されたコードです。

だから、私が見つけた:

この

は、バックグラウンドスレッドの節約を呼び出すコードは、これは

// merge changes from the context did save notification to the main context 
- (void)mergeChangesFromBackground:(NSNotification *)notification 
{ 
    // kill the listener 
    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext]; 

    NSThread *currentThread = [NSThread currentThread]; 

    // merge changes to the primary context, and wait for the action to complete on the main thread 
    [[self managedObjectContext] performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES]; 

    // dispatch the completion block 
    dispatch_async(dispatch_get_main_queue(), ^{ 

     // get objects from the database 
     NSMutableArray *objects = [[NSMutableArray alloc] init]; 
     for (id objectID in savedObjectIDs) { 
      NSError *error; 
      id object = [[self managedObjectContext] existingObjectWithID:objectID error:&error]; 
      if (error) { 
       [self logError:error]; 
      } else if (object) { 
       [objects addObject:object]; 
      } 
     } 

     // remove all saved object IDs from the array 
     [savedObjectIDs removeAllObjects]; 
     savedObjectClass = nil; 

     // call the completion block 
     //completion(objects); 
     saveCompletionBlock(objects); 

     // clear the saved completion block 
     saveCompletionBlock = nil; 
    }); 
} 

UPDATE NSManagedObjectContextDidSaveNotification通知によってで呼び出されたコードがある

// process in the background queue 
dispatch_async(backgroundQueue, ^(void){ 

    if (savedObjectIDs.count > 0) { 
     [savedObjectIDs removeAllObjects]; 
    } 
    if (savedObjectClass) { 
     savedObjectClass = nil; 
    } 

    // set the thead name 
    NSThread *currentThread = [NSThread currentThread]; 
    [currentThread setName:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME]; 

    // if there is not already a background context, then create one 
    if (!_backgroundQueueManagedObjectContext) { 
     NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 
     if (coordinator != nil) { 
      _backgroundQueueManagedObjectContext = [[NSManagedObjectContext alloc] init]; 
      [_backgroundQueueManagedObjectContext setPersistentStoreCoordinator:coordinator]; 
     } 
    } 

    // save the JSON dictionary starting at the upper most level of the key path 
    NSArray *objectIds = [self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundQueueManagedObjectContext level:0]; 

    // save the object IDs and the completion block to global variables so we can access them after the save 
    if (objectIds) { 
     [savedObjectIDs addObjectsFromArray:objectIds]; 
    } 
    if (completion) { 
     saveCompletionBlock = completion; 
    } 
    if (managedObjectClass) { 
     savedObjectClass = managedObjectClass; 
    } 

    // listen for the merge changes from context did save notification 
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChangesFromBackground:) name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext]; 

    // save all changes object context 
    [self saveManagedObjectContext]; 
}); 

処理していますソリューション。私がバックグラウンドスレッドにオブジェクトIDを保存しておき、メインスレッドでそれらを使って再フェッチしようとしていたのはうまくいかなかったということです。だから私は、NSManagedObjectContextDidSaveNotification通知で送信されたuserInfo辞書から挿入/更新されたオブジェクトを取得してしまいました。以下は、現在動作している私の更新されたコードです。

このコードは、前prossesingと保存ロジックを開始する前にのよう

// process in the background queue 
dispatch_async(backgroundQueue, ^(void){ 

    // set the thead name 
    NSThread *currentThread = [NSThread currentThread]; 
    [currentThread setName:VS_CORE_DATA_MANAGER_BACKGROUND_THREAD_NAME]; 

    [self logMessage:[NSString stringWithFormat:@"(%@) saveJSONObjects:objectMapping:class:completion:", [managedObjectClass description]]]; 

    // if there is not already a background context, then create one 
    if (!_backgroundQueueManagedObjectContext) { 
     NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 
     if (coordinator != nil) { 
      _backgroundQueueManagedObjectContext = [[NSManagedObjectContext alloc] init]; 
      [_backgroundQueueManagedObjectContext setPersistentStoreCoordinator:coordinator]; 
     } 
    } 

    // save the JSON dictionary starting at the upper most level of the key path 
    [self saveJSON:jsonDict objectMapping:objectMapping class:managedObjectClass managedObjectContext:_backgroundQueueManagedObjectContext level:0]; 

    // save the object IDs and the completion block to global variables so we can access them after the save 
    if (completion) { 
     saveCompletionBlock = completion; 
    } 

    // listen for the merge changes from context did save notification 
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChangesFromBackground:) name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext]; 

    // save all changes object context 
    [self saveManagedObjectContext]; 
}); 

背景にあなたの文章は、MOCので、これはあなたのケースでNSManagedObjectContextDidSaveNotification

- (void)mergeChangesFromBackground:(NSNotification *)notification 
{ 
    // kill the listener 
    [[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:_backgroundQueueManagedObjectContext]; 

    // merge changes to the primary context, and wait for the action to complete on the main thread 
    [[self managedObjectContext] performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES]; 

    // dispatch the completion block 
    dispatch_async(dispatch_get_main_queue(), ^{ 

     // pull the objects that were saved from the notification so we can get them on the main thread MOC 
     NSDictionary *userInfo = [notification userInfo]; 
     NSMutableArray *modifiedObjects = [[NSMutableArray alloc] init]; 
     NSSet *insertedObject = (NSSet *)[userInfo objectForKey:@"inserted"]; 
     NSSet *updatedObject = (NSSet *)[userInfo objectForKey:@"updated"]; 

     if (insertedObject && insertedObject.count > 0) { 
      [modifiedObjects addObjectsFromArray:[insertedObject allObjects]]; 
     } 
     if (updatedObject && updatedObject.count > 0) { 
      [modifiedObjects addObjectsFromArray:[updatedObject allObjects]]; 
     } 

     NSMutableArray *objects = [[NSMutableArray alloc] init]; 

     // iterate through the updated objects and find them in the main thread MOC 
     for (NSManagedObject *object in modifiedObjects) { 
      NSError *error; 
      NSManagedObject *obj = [[self managedObjectContext] existingObjectWithID:object.objectID error:&error]; 
      if (error) { 
       [self logError:error]; 
      } 
      if (obj) { 
       [objects addObject:obj]; 
      } 
     } 

     modifiedObjects = nil; 

     // call the completion block 
     saveCompletionBlock(objects); 

     // clear the saved completion block 
     saveCompletionBlock = nil; 
    }); 
} 

答えて

4

を処理する改良法でありますmergeChangesFromContextDidSaveNotificationの通知は、フォアグラウンドモックではなく、バックグラウンドmocで行われます。

したがって、バックグラウンドスレッドが背景mocオブジェクトに来ることについての通知を登録する必要があります。

この呼び出しを受け取ると、メインスレッドmocにメッセージを送信してmergeChangesFromContextDidSaveNotificationに送信できます。

アンドリュー

更新: ここで私はあなたがあなたのために働く答えを働いてきた参照

//register for this on the background thread 
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; 
    [nc addObserver:self selector:@selector(mergeChanges:) name:NSManagedObjectContextDidSaveNotification object:backgroundMOC]; 

- (void)mergeChanges:(NSNotification *)notification { 
    NSManagedObjectContext *mainThreadMOC = [singleton managedObjectContext]; 

    //this tells the main thread moc to run on the main thread, and merge in the changes there 
    [mainThreadMOC performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES]; 
} 
+0

お返事ありがとうございます。 saveManagedObjectContextメソッド呼び出しのすぐ上にリスナー(以下に掲載)を追加しようとしましたが、私はまだ同じ結果を得ています。オブザーバーを正しく追加しているのですか、オブザーバーを特定のスレッドに追加する別の方法がありますか? //コンテキストからのマージ変更をリッスンしました [[NSNotificationCenter defaultCenter] addObserver:自己セレクタ:@selector(mergeChangesFromContextDidSaveNotification :) name:NSManagedObjectContextDidSaveNotificationオブジェクト:_backgroundQueueManagedObjectContext]; –

+0

私はコードサンプルで答えを更新します –

+0

うーん...これは狂った問題です。私はあなたが提案した更新を行ったが、メインスレッドMOCからバックグラウンドMOCに保存された新しいオブジェクトをまだ取得していない。私は、ファクトリメソッドを使用してインスタンス化されたものとすべてを確認するためにメインスレッドのMOC呼び出しをすべて更新しましたが、保存されたオブジェクトはまだ見つかりません。私はまた、savedObjectIDs配列を出力して、IDを正しく取得しています。あなたは間違っているかもしれない何かを見ることができますか?助けてくれてありがとう!また、元の投稿を更新して、更新されたコードを取得しました。 –

6

を動作するはずのサンプルです。しかし、私はいくつかの同様の問題を抱えていると私の経験を共有し、あなたやこの状況を見て他の人に役立つかどうかを確認したいと思った。

マルチスレッドのコアデータは常に読みにくいですので、コードを誤解している場合は私をよろこんでください。しかし、あなたにはもっと簡単な答えがあるようです。

最初の試みでは、主なスレッドで使用する管理対象オブジェクトID(スレッド間で渡すことができるオブジェクト識別子と思われる)をグローバル変数に保存していたという重大な問題がありました。あなたはバックグラウンドスレッドでこれを行いました。問題は、バックグラウンドスレッドの管理オブジェクトコンテキストに保存する前に行ったことでした。オブジェクトIDは、保存する前に別のスレッド/コンテキストのペアに渡すことは安全ではありません。あなたが保存するときに変更することができます。オブジェクトIDのドキュメントの警告を参照してください:NSManagedObject reference

これを修正するには、バックグラウンドスレッドにセーブを通知し、そのスレッドの中で、現在の状態で使用されているため安全であることを把握します通知オブジェクトからオブジェクトIDを保存します。これらはメインスレッドに渡され、実際の変更もmergeChangesFromContextDidSaveNotificationの呼び出しでメインスレッドにマージされました。 1つまたは複数のステップを保存する場所は次のとおりです。

背景のスレッドでNSManagedObjectContextDidSaveNotificationを聞くために登録しています。同じ通知をメインスレッドで聞くように登録することができます。その通知では、メインスレッドで使用するのと同じオブジェクトIDを使用します。メインスレッドMOCは、mergeChangesFromContextDidSaveNotificationと渡された通知オブジェクトを使用して安全に更新できます。これは、メソッドがこのように動作するように設計されているためです:mergeChanges docs。いずれかのスレッドから完了ブロックを呼び出すことは、完了ブロックが呼び出されたスレッドにmocを一致させる限り、今や安全です。

メインスレッドですべてのメインスレッドを更新し、スレッドをきれいに分離し、更新されたものをパックして再パックしたり、同じ変更を二重保存して永続ストアに保存したりする必要がありません。

明白になるには - 起こったマージは管理対象オブジェクトのコンテキストとそのメモリ内の状態です - メインスレッドのmocはバックグラウンドスレッドのものと一致するように更新されますが、これらの変更をバックグラウンドスレッドのストアに保存しました。バックグラウンドスレッドで使用したときと同じように、通知オブジェクト内の更新されたオブジェクトにスレッドセーフなアクセス権があります。

あなたのソリューションはあなたのために働いていて、あなたは再因子化する必要はありませんが、これを見るかもしれない他の人のために私の考えを追加したいと思っています。あなたのコードを間違って解釈した場合は私に教えてください。

24

私はこれをそこに投げるつもりです。 コアデータプログラミングガイドにリストされている並行処理のベストプラクティスに従ってください。アップルは、ネストされたコンテキストを追加してからそれを更新していない。 (UIを処理するための適切な)https://developer.apple.com/videos/wwdc/2012/?id=214

セットアップあなたのメインスレッドを使用するように、プライマリコンテキスト:このビデオでは、完全な詳細に入る

NSManagedObjectContext * context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 
[context setPersistentStoreCoordinator:yourPSC]; 

をあなたはプライベートを作成し、それが同時操作を行うことが可能作成したオブジェクトの場合キュー・コンテキスト

NSManagedObjectContext * backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; 
[backgroundContext setParentContext:context]; 
//Use backgroundContext to insert/update... 
//Then just save the context, it will automatically sync to your primary context 
[backgroundContext save:nil]; 

を使用するQueueConcurrencyTypeは、コンテキストが、それは上(保存してフェッチ要求)の操作をフェッチしていますでしょうキューを参照します。 NSMainQueueConcurrencyTypeコンテキストは、メイン・キュー上で動作するすべてのものを処理し、UIの対話に適したものにします。 NSPrivateQueueConcurrencyTypeは、それ自身のプライベートキュー上でそれを行います。したがって、backgroundContextでsaveを呼び出すと、自動的にperformBlockを使用してparentContextを呼び出すプライベートデータがマージされます。デッドロックの原因となるメインスレッド上にある場合に備えて、プライベートキューコンテキストでperformBlockを呼び出すことは望ましくありません。

本当に欲しいのであれば、プライベートキューの同時実行性のタイプ(バックグラウンドでの保存に適しています)として、メインのキューコンテキストをメインUI用に、次にメインコンテキストをメインコンテキストとして作成することができますバックグラウンド操作(インポートなど)のために。

関連する問題