2

非常に大きな Core Data ツリー構造の描画を CAtiledLayer にオフロードする方法を考えています。CATiledLayer は、バックグラウンド スレッドで描画を実行し、描画されるたびにタイルをフェードインするため、素晴らしいようです。ただし、描画の情報は設計上スレッド セーフではない Core Data コンテキストから取得されるため、描画コードが CD コンテキストにアクセスする必要があるという競合状態の問題が発生しています。

通常、Core Data でバックグラウンド タスクを実行する必要がある場合は、バックグラウンド スレッドで新しいコンテキストを作成し、既存のモデルと永続ストア コーディネーターを再利用して、スレッド化の問題を回避します。しかし、CATiledLayer はすべてのスレッド化を内部で行うため、コンテキストを作成するタイミングがわかりません。なんらかのコンテキスト共有が必要です。そうしないと、最初から適切なエンティティを CATiledLayer に渡すことができません。

このシナリオに対処する方法を提案している人はいますか?

乾杯、エリック・ポール。

4

2 に答える 2

1

最も簡単な解決策は、ディスパッチ API を使用してすべてのデータ アクセスを 1 つのスレッドにロックする一方で、実際の描画をマルチスレッドにできるようにすることです。

既存の管理対象オブジェクト コンテキストにメイン スレッドでしかアクセスできない場合は、次のようにします。

- (void)drawInContext:(CGContextRef)context // I'm using a CATiledLayer subclass. You might be using a layer delegate instead
{
  // fetch data from main thread
  __block NSString *foo;
  __block NSString *bar;
  dispatch_sync(dispatch_get_main_queue(), ^{
    NSManagedObject *record = self.managedObjecToDraw;
    foo = record.foo;
    bar = record.bar;
  });

  // do drawing here
}

これは迅速かつ簡単な解決策ですが、データのフェッチ中にメイン スレッドがロックされ、スクロール中に新しいタイルが読み込まれるたびに「ヒッチ」が発生することはほぼ確実です。これを解決するには、すべてのデータ アクセスを「シリアル」ディスパッチ キューで実行する必要があります。

キューには独自の管理対象オブジェクト コンテキストが必要であり、このコンテキストを、(おそらく) ユーザー アクションによって更新されるメイン スレッドのコンテキストと同期させる必要があります。これを行う最も簡単な方法は、コンテキストが変更されたという通知を観察し、描画に使用されたものを破棄することです。

キューのインスタンス変数を定義します。

@interface MyClass
{
  NSManagedObjectContext *layerDataAccessContext;
  dispatch_queue_t layerDataAccessQueue;
}
@end

init メソッドで作成します。

- (id)init
{
  layerDataAccessQueue = dispatch_queue_create("layer data access queue", DISPATCH_QUEUE_SERIAL);

  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextDidChange:) name:NSManagedObjectContextDidSaveNotification object:nil]; // you might want to create your own notification here, which is only sent when data that's actually being drawn has changed
}

- (void)contextDidChange:(NSNotification *)notif
{
  dispatch_sync(layerDataAccessQueue, ^{
    [layerDataAccessContext release];
    layerDataAccessContext = nil;
  });
  [self.layer setNeedsDisplay];
}

描画中にコンテキストにアクセスします。

- (void)drawInContext:(CGContextRef)context
{
  // fetch data from main thread
  __block NSString *foo;
  __block NSString *bar;
  dispatch_sync(layerDataAccessQueue, ^{
    NSManagedObject record = self.managedObjectToDraw;
    foo = record.foo;
    bar = record.bar;
  });

  // do drawing here
}

- (NSManagedObject *)managedObjectToDraw
{
  if (!layerDataAccessContext) {
    __block NSPersistentStoreCoordinator *coordinator;
    dispatch_sync(dispatch_get_main_queue(), ^{
      coordinator = [self persistentStoreCoordinator];
    });

    layerDataAccessContext = [[NSManagedObjectContext alloc] init];
    [layerDataAccessContext setPersistentStoreCoordinator:coordinator];
  }

  NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
  NSEntityDescription *entity =
      [NSEntityDescription entityForName:@"Employee"
              inManagedObjectContext:layerDataAccessContext];
  [request setEntity:entity];

  NSPredicate *predicate =
      [NSPredicate predicateWithFormat:@"self == %@", targetObject];
  [request setPredicate:predicate];

  NSError *error = nil;
  NSArray *array = [layerDataAccessContext executeFetchRequest:request error:&error];
  NSManagedObject *record;
  if (array == nil || array.count == 0) {
    // Deal with error.
  }

  return [array objectAtIndex:0];
}
于 2012-01-20T21:09:45.203 に答える
0

CATiledLayerの描画間で管理対象オブジェクトのコンテキストインスタンスを共有することを諦めましたdrawLayer:inContext:が、描画はすでに非同期であるため、パフォーマンスへの影響は目立ちません。

より良い解決策を持っている人がいたら、共有してください!

于 2011-02-24T12:21:23.410 に答える