Как правильно обрабатывать потоки при рисовании информации сущности Core Data с помощью CATiledLayer - PullRequest
2 голосов
/ 19 февраля 2011

Я обдумываю, как перенести чертеж очень большой структуры дерева базовых данных в CATiledLayer. CATiledLayer кажется удивительным, потому что он выполняет рисование в фоновом потоке, а затем затухает в плитках, когда они рисуются. Однако, поскольку информация о чертеже взята из контекста Core Data, который по своей конструкции не является поточно-ориентированным, я сталкиваюсь с проблемами состояния гонки, когда код чертежа должен обращаться к контексту CD.

Обычно, если мне нужно выполнить фоновые задачи с Core Data, я создаю новый контекст в фоновом потоке и повторно использую существующую модель и постоянный координатор хранилища, чтобы предотвратить проблемы с потоками. Но CATiledLayer выполняет все потоки внутри, поэтому я не знаю, когда создавать контекст, и должен быть какой-то общий контекст, или я не могу передать нужные объекты в CATiledLayer для начала.

Есть ли у кого-нибудь предложения, как мне справиться с этим сценарием?

Ура, Эрик-Поль.

Ответы [ 2 ]

1 голос
/ 21 января 2012

Самое простое решение - использовать диспетчерский API для блокировки доступа к вашим данным в одном потоке, в то же время позволяя фактическому рисунку быть многопоточным.

Если ваш существующий контекст управляемого объекта доступен только в главном потоке, то это то, что вы делаете:

- (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];
}
0 голосов
/ 24 февраля 2011

Я прекратил попытки обмениваться экземплярами контекста управляемого объекта между рисованиями CATiledLayer и теперь просто выделяю / инициализирую новый контекст при каждом вызове drawLayer:inContext: Падение производительности не заметно, поскольку рисунок уже асинхронный.

Если есть кто-нибудь с лучшим решением, пожалуйста, поделитесь!

...