【问题标题】:Generic approach to NSManagedObjectContext in multi-threaded application多线程应用程序中 NSManagedObjectContext 的通用方法
【发布时间】:2011-03-29 08:18:24
【问题描述】:

我在这里阅读了许多关于 NSManagedObjectContext 和多线程应用程序的帖子。我还查看了 CoreDataBooks 示例以了解单独的线程如何需要它们自己的 NSManagedObjectContext,以及保存操作如何与主 NSManagedObjectContext 合并。我发现这个例子很好,但也太具体了。我试图概括这一点,并想知道我的方法是否合理。

我的方法是使用通用函数来获取当前线程的 NSManagedObjectContext。该函数返回主线程的 NSManagedObjectContext,但如果从不同的线程中调用,它将创建一个新的(或从缓存中获取)。如下:

+(NSManagedObjectContext *)managedObjectContext {
    MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
    NSManagedObjectContext *moc = delegate.managedObjectContext;

    NSThread *thread = [NSThread currentThread];

    if ([thread isMainThread]) {
        return moc;
    }

    // a key to cache the context for the given thread
    NSString *threadKey = [NSString stringWithFormat:@"%p", thread];

    // delegate.managedObjectContexts is a mutable dictionary in the app delegate
    NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts;

    if ( [managedObjectContexts objectForKey:threadKey] == nil ) {
        // create a context for this thread
        NSManagedObjectContext *threadContext = [[[NSManagedObjectContext alloc] init] autorelease];
        [threadContext setPersistentStoreCoordinator:[moc persistentStoreCoordinator]];
        // cache the context for this thread
        [managedObjectContexts setObject:threadContext forKey:threadKey];
    }

    return [managedObjectContexts objectForKey:threadKey];
}

如果从主线程调用,保存操作很简单。从其他线程调用的保存操作需要在主线程中合并。为此,我有一个通用的 commit 函数:

+(void)commit {
    // get the moc for this thread
    NSManagedObjectContext *moc = [self managedObjectContext];

    NSThread *thread = [NSThread currentThread];

    if ([thread isMainThread] == NO) {
        // only observe notifications other than the main thread
        [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(contextDidSave:)
                                                 name:NSManagedObjectContextDidSaveNotification
                                               object:moc];
    }

    NSError *error;
    if (![moc save:&error]) {
        // fail
    }

    if ([thread isMainThread] == NO) {
        [[NSNotificationCenter defaultCenter] removeObserver:self 
                                                    name:NSManagedObjectContextDidSaveNotification 
                                                  object:moc];
    }
}

如果commit 中的通知调用,我们在contextDidSave: 函数中执行合并。

+(void)contextDidSave:(NSNotification*)saveNotification {
    MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
    NSManagedObjectContext *moc = delegate.managedObjectContext;

    [moc performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:)
                      withObject:saveNotification
                   waitUntilDone:YES];
}

最后,我们用这个清理 NSManagedObjectContext 的缓存:

+(void)initialize {
    [[NSNotificationCenter defaultCenter] addObserver:self 
                                             selector:@selector(threadExit) 
                                                 name:NSThreadWillExitNotification 
                                               object:nil]; 
}

+(void)threadExit {
    MyAppDelegate *delegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
    NSString *threadKey = [NSString stringWithFormat:@"%p", [NSThread currentThread]];  
    NSMutableDictionary *managedObjectContexts = delegate.managedObjectContexts;    

    [managedObjectContexts removeObjectForKey:threadKey];
}

这可以编译并且似乎可以工作,但我知道由于竞争条件,线程问题可能会很棘手。有人认为这种方法有问题吗?

另外,我在异步请求的上下文中使用它(使用 ASIHTTPRequest),它从服务器获取一些数据并更新和插入 iPhone 上的存储。请求完成后似乎 NSThreadWillExitNotification 不会触发,然后将同一线程用于后续请求。这意味着相同的 NSManagedObjectContext 用于同一线程上的不同请求。这是个问题吗?

【问题讨论】:

  • 克里斯,我在使用在主线程中创建的单个 NSManagedObjectContext 用于 NSoperation 队列中的所有操作时遇到了类似的多线程问题。当每个线程尝试保存上下文时,问题就出现了,应用程序随机崩溃并从核心数据中抛出异常。我正在考虑在所有操作中使用时锁定此上下文,以便每个操作都具有对上下文的独占访问权限。我已经阅读了您上面的解决方案。听起来 gud ,请您粘贴我用于合并上下文的新代码,还请评论使用锁进行播放

标签: iphone objective-c multithreading core-data asihttprequest


【解决方案1】:

在发布这个问题一年后,我终于构建了一个框架来概括和简化我使用 Core Data 的工作。它超越了最初的问题,并添加了许多功能,使 Core Data 交互更加容易。详情在这里:https://github.com/chriscdn/RHManagedObject

【讨论】:

    【解决方案2】:

    我在最终更好地理解问题后找到了解决方案。我的解决方案并没有直接解决上述问题,但确实解决了我为什么必须首先处理线程的问题。

    我的应用程序使用 ASIHTTPRequest 库来处理异步请求。我从服务器获取一些数据,并使用委托requestFinished 函数来添加/修改/删除我的核心数据对象。 requestFinished 函数在不同的线程中运行,我认为这是异步请求的自然副作用。

    深入挖掘后,我发现 ASIHTTPRequest 故意在单独的线程中运行请求,但可以在我的 ASIHTTPRequest 子类中覆盖:

    +(NSThread *)threadForRequest:(ASIHTTPRequest *)request {
        return [NSThread mainThread];
    }
    

    这个小改动将requestFinished 放在主线程中,这样我就不再需要关心应用程序中的线程了。

    【讨论】:

    • 我不太确定我是否理解了。 ASIHTTPRequest 确实为异步请求使用了一个单独的线程(实际上是一个 NSOperationQueue),但同样它总是在主线程上运行 requestFinished。 (在最新的代码中,此功能在 callSelectorOnMainThread 方法中。)也就是说我看不出您的解决方案有任何缺点。
    • 我发现 requestFinished 并没有在主线程上运行,直到我添加了上面那三行。这有可能随着 ASIHTTPRequest 而改变吗?我正在使用 v1.7。
    • 你是对的。 ASIHTTPRequest 委托在主线程上运行,但我正在实现 ASIHTTPRequest 的子类并将我的代码放在 requestFinished: 方法中。这不一定会在主线程上调用。谢谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-14
    • 2015-09-29
    • 1970-01-01
    • 2010-11-30
    • 1970-01-01
    相关资源
    最近更新 更多