代码之家  ›  专栏  ›  技术社区  ›  Mustafa

带有CoreData的中央调度(GCD)

  •  22
  • Mustafa  · 技术社区  · 15 年前

    我在应用程序中使用Grand Central Dispatch(GCD)来做一些繁重的工作。应用程序正在使用核心数据进行数据存储。以下是我的设想(以及相关问题):

    dispatch_queue_t main_queue = dispatch_get_main_queue();
    dispatch_queue_t request_queue = dispatch_queue_create("com.app.request", NULL);
    
    dispatch_async(request_queue, ^{
        MyNSManagedObject *mObject = [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
    
        // … 
        // <heavy lifting>
        // … 
    
        // … 
        // <update mObject>
        // … 
    
        [self saveManagedObjectContext];
    });     
    

    由于 [self saveManagedObjectContext] , fetchResultsController 委托方法是自动调用的。因此,UI更新逻辑启动。

    现在我的问题是,我需要用 main_queue 对于 -saveManagedObjectContext ? 我应该对我的 NSManagedObject 在里面 主要队列 ? 更新 被管理对象 可能需要2-3秒。请告知。

    3 回复  |  直到 14 年前
        1
  •  17
  •   Robert Höglund    15 年前

    正如您可能知道或注意到的,您必须在主线程上执行UI操作。正如您所提到的,当您保存UI更新时。你可以通过嵌套一个调用来解决这个问题 dispatch_sync 在主线上。

    dispatch_queue_t main_queue = dispatch_get_main_queue();
    dispatch_queue_t request_queue = dispatch_queue_create("com.app.request", NULL);
    
    __block __typeof__(self) blockSelf = self;
    
    dispatch_async(request_queue, ^{
        MyNSManagedObject *mObject = [blockSelf.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
    
        // update and heavy lifting...
    
        dispatch_sync(main_queue, ^{
          [blockSelf saveManagedObjectContext];
        });
    });     
    

    使用 blockSelf 是为了避免意外创建引用循环。 ( Practical blocks )

        2
  •  60
  •   Mike Weller    15 年前

    对于核心数据,有一条黄金法则——每个线程一个托管对象上下文。托管对象上下文不是线程安全的,因此如果在后台任务中工作,可以使用主线程避免与UI操作的线程冲突,也可以创建一个新上下文来完成工作。如果这项工作需要几秒钟的时间,那么您应该执行后者来阻止您的UI锁定。

    要执行此操作,请创建一个新上下文,并为其提供与主上下文相同的持久存储:

    NSManagedObjectContext *backgroundContext = [[[NSManagedObjectContext alloc] init] autorelease];
    [backgroundContext setPersistentStoreCoordinator:[mainContext persistentStoreCoordinator]];
    

    执行所需的任何操作,然后在保存新上下文时,需要处理保存通知并将更改合并到主上下文中 mergeChangesFromContextDidSaveNotification: 信息。代码应该如下所示:

    /* Save notification handler for the background context */
    - (void)backgroundContextDidSave:(NSNotification *)notification {
        /* Make sure we're on the main thread when updating the main context */
        if (![NSThread isMainThread]) {
            [self performSelectorOnMainThread:@selector(backgroundContextDidSave:)
                                   withObject:notification
                                waitUntilDone:NO];
            return;
        }
    
        /* merge in the changes to the main context */
        [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    }
    
    /* ... */
    
    /* Save the background context and handle the save notification */
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(backgroundContextDidSave:)
                                                 name:NSManagedObjectContextDidSaveNotification
                                               object:backgroundContext];
    
    [backgroundContext save:NULL];
    
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:NSManagedObjectContextDidSaveNotification
                                                  object:syncContext];
    

    处理保存通知和合并很重要,否则主UI/上下文将看不到您所做的更改。通过合并,您的主fetchresultcontroller等将获得更改事件,并按预期更新您的UI。

    另一个需要注意的重要事项是,NSManagedObject实例只能在从中获取它们的上下文中使用。如果您的操作需要对某个对象的引用,则必须传递该对象的 objectID 使用 existingObjectWithID: . 比如说:

    /* This can only be used in operations on the main context */
    MyNSManagedObject *objectInMainContext =
        [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
    
    /* This can now be used in your background context */
    MyNSManagedObject *objectInBackgroundContext =
        (MyNSManagedObject *) [backgroundContext existingObjectWithID:[objectInMainContext objectID]];
    
        3
  •  0
  •   Resh32    13 年前

    由于核心数据要求每个线程有一个托管对象上下文,可能的解决方案是在全局管理器中跟踪每个线程的上下文,然后跟踪保存通知并传播到所有线程:

    假设:

    @property (nonatomic, strong) NSDictionary* threadsDictionary;
    

    下面是如何获取托管对象(每个线程):

    - (NSManagedObjectContext *) managedObjectContextForThread {
    
    // Per thread, give one back
    NSString* threadName = [NSString stringWithFormat:@"%d",[NSThread currentThread].hash];
    
    NSManagedObjectContext * existingContext = [self.threadsDictionary objectForKey:threadName];
    if (existingContext==nil){
        existingContext = [[NSManagedObjectContext alloc] init];
        [existingContext setPersistentStoreCoordinator: [self persistentStoreCoordinator]];
        [self.threadsDictionary setValue:existingContext forKey:threadName];
    }
    
    return existingContext;
    

    }

    在全局管理器的init方法中的某个时刻(我使用了一个singleton):

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundContextDidSave:)                                                    name:NSManagedObjectContextDidSaveNotification                                                   object:nil];
    

    然后接收保存通知并传播到所有其他托管上下文对象:

    - (void)backgroundContextDidSave:(NSNotification *)notification {
        /* Make sure we're on the main thread when updating the main context */
        if (![NSThread isMainThread]) {
            [self performSelectorOnMainThread:@selector(backgroundContextDidSave:)
                                   withObject:notification
                                waitUntilDone:NO];
            return;
        }
    
        /* merge in the changes to the main context */
        for (NSManagedObjectContext* context in [self.threadsDictionary allValues]){
                [context mergeChangesFromContextDidSaveNotification:notification];
        }
    }
    

    (为清楚起见,删除了一些其他方法)