2017-08-02 18 views
1

SETUPそれは手動でセットアップCoreDataのこのようなスタックで、古いアプリだ奇妙な問題が

を(あなたはこれを後で読んで、最初のシナリオのセクションをスキップすることができます)その後、再帰的にコンテキストを保存します。

+ (void)saveContext:(NSManagedObjectContext *)context 
{ 
    [context performBlockAndWait:^{ 
     if (context.hasChanges && context.persistentStoreCoordinator.persistentStores.count) { 
      NSError *error = nil; 

      if ([context save:&error]) { 
       NSLog(@"saved context: %@", context); 

       // Recursive save parent context. 
       if (context.parentContext) [self saveContext:context.parentContext]; 
      } 
      else { 
       // do some real error handling 
       NSLog(@"Could not save master context due to %@", error); 
      } 
     } 
    }]; 
} 

SCENARIO>masterContext - - >persistentStoreサーバからのデータの

アプリ負荷ロットは、その後newContext第1内側更新を実行し、mainContext合流。

多くのデータがあるため、同期プロセスは約10個の非同期スレッドに分割されています。>一度に10個のnewContextがあります。

今、データは複雑で、parents <-> children (same class)のようなものです。 1 parentは、多くの場合、childrenchildの場合、mother, father, god father, step mother...を持つことができるので、n-n relationshipです。まず、parentを取得し、childを取得してからchildparentに設定します。

サーバーはちょっと愚かです。無効なオブジェクトを送信することはできません。しかし、顧客は、バックエンドからアプリケーションのオブジェクトの表示を制御したいと思いますので、私はそれを行うための2つの特性を有する:

  1. hasUpdated:ロードプロセスの開始時に、バッチ更新を実行し、すべてのオブジェクトのhasUpdatedを設定しますいいえ。サーバーからデータを取得したら、このプロパティーをYESに更新します。
  2. isActive:すべての読み込みが完了したら、hasUpdate == NOの場合、このプロパティをNOにバッチ更新してください。その後、私はいくつかのオブジェクトがは、彼らがバックエンドで有効している場合でも、不足しているされている理由

    お客様は文句isActive == NO

ISSUEでオブジェクトが表示されませんフィルターを持っています。

  1. newContext.updatedObjects::{obj1.ID = 100、hasUpdated == YES}
  2. "保存newContextを"
  3. はmainContext.updatedObjectsを:{私は長い間の後、この奇妙な問題になったための闘争とデバッグをしましたobj1.ID = 100、hasUpdated == NO}

//ここでやります。明らかに、マスターは更新されました= NOそして最後にisActiveはnoに設定され、欠落したオブジェクトが発生します。

毎回起こったことがあれば、おそらく修正しやすくなります(おそらく?)。

  • 初めて実行されている(最初の時点で、私はappDidFinishLaunch...が呼ばれました場所からアプリの起動を意味する):すべて正しい
  • 2回目:欠落している(153個のオブジェクト)
  • 3回目しかし、それは次のように発生しました:すべて正しい
  • 4回目:欠落している(153個のオブジェクト)(?もう一度、正確にそれらの複数の両親と、私はそう信じています!)
  • 5回目:再び
  • 正しい...ようにします。
また、同じコンテキスト(同じnewContext)を持つオブジェクトでこのように見えます。信じられない。

質問

ですが、なぜでしょうか?これをどうやって解決するのですか?それらのオブジェクトに子供がいなければ、私の人生はもっと簡単になります!!!!

BONUSあなたはバッチ更新があるか知りたい場合には

は、それが以下です。注:

  1. ダウンロード要求は、非同期キューにある:_shareInstance.apiQueue = dispatch_queue_create("product_request_queue", DISPATCH_QUEUE_CONCURRENT);
  2. 解析の応答と更新のプロパティは、キューにsyncronousです:完全に解析するたびに_shareInstance.saveQueue = dispatch_queue_create("product_save_queue", DISPATCH_QUEUE_SERIAL);
  3. 、私はnewContext保存を実行し、同じシリアルでupdateProductActiveStatus:を求めますキュー。すべての要求が完了したら、バッチ更新ステータスを実行します。リクエストはコンカレントキューで処理されるので、セーブ(シリアル)キューよりも早く終了するので、それはかなり愚かなプルーフ処理です。

コード:

// Load Manager 
- (void)resetProductUpdatedStatus 
{ 
    NSBatchUpdateRequest *request = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:NSStringFromClass([Product class])]; 
    request.propertiesToUpdate = @{ @"hasUpdated" : @(NO) }; 
    request.resultType = NSUpdatedObjectsCountResultType; 

    NSBatchUpdateResult *result = (NSBatchUpdateResult *)[self.masterContext executeRequest:request error:nil]; 

    NSLog(@"Batch update hasUpdated: %@", result.result); 

    [self.masterContext performBlockAndWait:^{ 
     [self.masterContext refreshAllObjects]; 

     [[CoreDataUtil managedObjectContext] performBlockAndWait:^{ 
      [[CoreDataUtil managedObjectContext] refreshAllObjects]; 
     }]; 
    }]; 
} 

- (void)updateProductActiveStatus:(SyncComplete)callback 
{ 
    if (self.apiRequestList.count) return; 

    NSBatchUpdateRequest *request = [NSBatchUpdateRequest batchUpdateRequestWithEntityName:NSStringFromClass([Product class])]; 
    request.predicate = [NSPredicate predicateWithFormat:@"hasUpdated = NO AND isActive = YES"]; 
    request.propertiesToUpdate = @{ @"isActive" : @(NO) }; 
    request.resultType = NSUpdatedObjectsCountResultType; 

    NSBatchUpdateResult *result = (NSBatchUpdateResult *)[self.masterContext executeRequest:request error:nil]; 
    NSLog(@"Batch update isActive: %@", result.result); 

    [self.masterContext performBlockAndWait:^{ 
     [self.masterContext refreshAllObjects]; 

     NSManagedObjectContext *maincontext = [CoreDataUtil managedObjectContext]; 
     NSLog(@"Refreshed master"); 

     [maincontext performBlockAndWait:^{ 
      [maincontext refreshAllObjects]; 

      NSLog(@"Refreshed main"); 

      // Callback 
      if (callback) dispatch_async(dispatch_get_main_queue(), ^{ callback(YES, nil); }); 
     }]; 
    }]; 
} 

答えて

1

mergePolicyは悪です。唯一の正しいmergePolicyはです。それ以外のポリシーでは、core-dataに静かに失敗するように要求していて、期待したときには更新しません。

あなたの問題は、バックグラウンドコンテキストでコアデータに同時に書き込んでいることが原因と思われます。 (私はあなたがシリアルキューを持っていると知っていることを知っていますが、performBlockをキュー内で呼び出すと、各ブロックは同時に実行されます)。競合があった場合は、上書きされます。 1つの同期方法でコアデータに書き込む必要があります。

NSPersistentContainerでこれを達成する方法についての回答を書いた: NSPersistentContainer concurrency for saving to core dataあなたのコードをそれに移行することをお勧めします。それは本当にそれほど難しいはずがありません。

コードをできるだけ近いものにして、それほど難しくないようにしたい場合は、

シリアル動作キューください:

_persistentContainerQueue = [[NSOperationQueue alloc] init]; 
_persistentContainerQueue.maxConcurrentOperationCount = 1; 

そして、すべてこのキューを使用して書いてください:

- (void)enqueueCoreDataBlock:(void (^)(NSManagedObjectContext* context))block{ 
    void (^blockCopy)(NSManagedObjectContext*) = [block copy]; 

    [self.persistentContainerQueue addOperation:[NSBlockOperation blockOperationWithBlock:^{ 
     NSManagedObjectContext* context = [CoreDataUtil newManagedObjectContext]; 
     [context performBlockAndWait:^{ 
      blockCopy(context); 
      [CoreDataUtil saveContext:context]; 
     }]; 
    }]]; 
} 

また、それは、オブジェクトが更新されるかもしれないが、あなたはので、それを見ていないがfetchedResultsControllerに依存して更新されます。また、fetchedResultsControllerはバッチ更新要求から更新しません。

+0

あなたの答えをありがとう、私はこれを移行し、結果を教えようとします!あなたの最終的なコメントとして、私はそれがここに当てはまらないことを100%確信しています。データリストが正しく更新されました。そのため、顧客は同期後にデータが表示され消えてしまうことを訴えています。また、 'NSErrorMergePolicy'をどう扱うべきか教えてください。それは '[context save:&error]'エラー分岐に入るでしょうか? – Eddie

+0

この問題に苦しんで数日(ほぼ半月)後、私は諦めました。 'MagicalRecord'を使って簡単にマイグレーションし、' NSFetchRequest'を使ってバッチ更新を行います。フェッチして保存します。何とか 'enqueueCoreDataBlock:'を使って、すべての更新と保存を行います。あなたの仕事をありがとう:( – Eddie