使用CATiledLayer绘制Core Data实体信息时如何正确处理线程

发布于 2024-10-18 07:42:24 字数 346 浏览 5 评论 0原文

我正在考虑如何将非常大的核心数据树结构的绘制卸载到 CATiledLayer。 CATiledLayer 似乎很棒,因为它在后台线程上执行绘图,然后在绘制时淡入图块。但是,由于绘图的信息来自设计为非线程安全的核心数据上下文,因此我遇到了绘图代码需要访问 CD 上下文的竞争条件问题。

通常,如果我需要使用 Core Data 执行后台任务,我会在后台线程中创建一个新上下文并重用现有模型和持久存储协调器,以防止线程问题。但是CATiledLayer在内部完成所有线程,所以我不知道何时创建上下文,并且需要某种上下文共享,否则我无法将正确的实体传递给CATiledLayer。

有人建议我如何处理这种情况吗?

干杯, 埃里克-保罗.

I'm contemplating how to offload the drawing of a very large Core Data tree structure to CATiledLayer. CATiledLayer seems to be awesome because it performs drawing on a background thread and then fades in tiles whenever they're drawn. However, because the information of the drawing comes from a Core Data context that is by design not thread safe, I'm running into race condition issues where the drawing code needs to access the CD context.

Normally, if I need to perform background tasks with Core Data, I create a new context in the background thread and reuse the existing model and persistent store coordinator, to prevent threading issues. But the CATiledLayer does all the threading internally, so I don't know when to create the context, and there needs to be some kind of context sharing, or I can't pass the right entities to the CATiledLayer to begin with.

Is there anyone with a suggestion how I can deal with this scenario?

Cheers,
Eric-Paul.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

最好是你 2024-10-25 07:42:24

最简单的解决方案是使用调度 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];
}

The easiest solution is to use the dispatch API to lock all of your data access onto a single thread, while still allowing the actual drawing to be multi-threaded.

If your existing managed object context can only be accessed on the main thread, then this is what you do:

- (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
}

This is a quick and easy solution, but it will lock your main thread while fetching the data, which is almost certainly going to create "hitches" whenever a new tile is loaded while scrolling around. To solve this, you need to perform all of your data access on a "serial" dispatch queue.

The queue needs to have it's own managed object context, and you need to keep this context in sync with the context on your main thread, which is (presumably) being updated by user actions. The easiest way to do this is to observe a notification that the context has changed, and throw out the one used for drawing.

Define an instance variable for the queue:

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

Create it in your init method:

- (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];
}

And access the context while drawing:

- (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];
}
梦明 2024-10-25 07:42:24

我已经放弃尝试在 CATiledLayer 绘制之间共享托管对象上下文实例,现在只需在每次调用 drawLayer:inContext: 时分配/初始化一个新上下文。性能影响并不明显,因为绘图是已经异步了。

如果有人有更好的解决方案,请分享!

I've given up trying to share managed object context instances between CATiledLayer draws and now just alloc/init a new context at every call of drawLayer:inContext: The performance hit is not noticable, for the drawing is already asynchronous.

If there's anyone out there with a better solution, please share!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文