iphone - 如何从不同线程更改 Core Data 对象,而无需在每次更改后保存

标签 iphone multithreading core-data

我已经仔细阅读了 SO 上的所有相关线程,但仍然对如何从多个线程对核心数据对象进行更改而不必在每次更改后进行保存感到困惑。

我正在开发一个不断与服务器通信的应用程序。该应用程序使用 Core Data 进行存储,并在一些 View Controller 中使用 NSFetchedResultsController 来从持久性存储中获取数据。通常当用户执行某个操作时,会触发网络请求。在发送网络请求之前,通常应对相关的Core Data对象进行一些更改,并且在服务器响应时,将对这些Core Data对象进行更多更改。

最初,所有核心数据操作都是在同一个 NSManagedObjectContext 中的主线程上完成的。一切都很好,只是当网络流量很高时,应用程序可能会变得无响应几秒钟。显然这是 Not Acceptable ,因此我考虑将一些核心数据操作移至后台运行。

我尝试的第一种方法是创建一个 NSOperation 对象来处理每个网络响应。在 NSOperation 对象的 main 方法中,我设置了一个专用的 MOC,进行一些更改,并在最后提交更改。

- (void)main
{
    @try {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

        // Create a dedicated MOC for this NSOperation
        NSManagedObjectContext * context = [[NSManagedObjectContext alloc] init];
        [context setPersistentStoreCoordinator:[APP_DELEGATE persistentStoreCoordinator]];

        // Make change to Core Data objects
        // ...

        // Commit the changes
        NSError *error = nil;
        if ([context hasChanges] && ![context save:&error]) {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        }

        // Release the MOC
        [context release];

        // Drain the pool
        [pool drain];
    }
    @catch (NSException *exception) {
        // Important that we don't rethrow exception here
        NSLog(@"Exception: %@", exception);
    }
}

主线程上的 MOC 已为 NSManagedObjectContextDidSaveNotification 注册。

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

因此,当后台上下文提交更改时,主 MOC 将收到通知,然后合并更改:

- (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 the changes into the main context
    [[self managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];
}

但是,正如我之前提到的,我还需要从主 MOC 对核心数据对象进行更改。每一项更改通常都很小(例如更新对象中的一个实例变量),但可能会有很多更改。所以我真的不想在每次更改后都保存主要的 MOC。但如果我不这样做,在将更改从后台 MOC 合并到主 MOC 时就会遇到问题。由于两个 MOC 都有未保存的更改,因此会发生合并冲突。设置合并策略也没有帮助,因为我想保留两个 MOC 的更改。

一种可能性是也使用 NSManagedObjectContextDidSaveNotification 注册后台 MOC,但这种方法对我来说听起来像是糟糕的设计。每次更改后我仍然需要保存主要 MOC。

我尝试的第二种方法是从在永久后台线程上运行的专用后台上下文中执行所有核心数据更改。

- (NSThread *)backgroundThread
{
    if (backgroundThread_ == nil) {
        backgroundThread_ = [[NSThread alloc] initWithTarget:self selector:@selector(backgroundThreadMain) object:nil];
        // Actually start the thread
        [backgroundThread_ start];
    }
    return backgroundThread_;
}

// Entry point of the background thread
- (void)backgroundThreadMain
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // We can't run the runloop unless it has an associated input source or a timer, so we'll just create a timer that will never fire.
    [NSTimer scheduledTimerWithTimeInterval:DBL_MAX target:self selector:@selector(ignore) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] run];

    // Create a dedicated NSManagedObjectContext for this thread.
    backgroundContext_ = [[NSManagedObjectContext alloc] init];
    [backgroundContext_ setPersistentStoreCoordinator:[self persistentStoreCoordinator]];

    [pool drain];
}

因此,每当我需要从主线程更改 Core Data 时,我都必须从主线程获取 objectID,然后传递给后台线程来执行更改。当后台上下文保存时,更改将被合并回主 MOC。

- (void)addProduct:(Product *)product toCatalog:(Catalog *)catalog;

将更改为:

- (void)addProduct:(NSManagedObjectID *)productObjectId toCatalog:(NSManagedObjectID *)catalogObjectId
{
    NSArray * params = [NSArray dictionaryWithObjects:productObjectId, catalogObjectId, nil];
    [self performSelector:(addProductToCatalogInBackground:) onThread:backgroundThread_ withObject:params waitUntilDone:NO];
}

但这看起来很复杂而且丑陋。编写这样的代码似乎首先就否定了使用 Core Data 的用处。另外,我仍然需要在每次更改后保存 MOC,因为如果不先将新对象保存到数据存储中,我就无法获取新对象的 objectId。

我觉得我错过了一些东西。我真的希望有人能对此有所启发。谢谢。

最佳答案

NSManagedObjectContext 只是一个便笺簿。保存它的行为会将暂存器中的更改向下移动到 NSPersistentStoreCoordinator 并可能向下移动到磁盘。一个 MOC 可以了解另一个 MOC 的更改的唯一方法是通过 NSPercientStoreCoordinator。因此需要保存。然而,在下一版本的 iOS 中,保存的成本要低得多。

如果您必须符合 iOS4 或更低版本,那么保存是唯一的选择。但是,您可以加载保存内容并降低执行频率,具体取决于应用程序的设计。如果您要导入数据,请在导入完成时保存或以导入内部的逻辑单元保存。不需要每次输入后都保存,这样很浪费。

顺便说一句,我建议使用 NSOperation 实例,而不是直接使用 NSThread 实例。他们更容易合作并且表现更好。

此外,您不需要将 Objective-C 代码包装在 try/catch block 中。很少有事情会抛出异常;尤其是在 iOS 上。

最后,我建议看一下 my post on CIMGF关于在后台线程上导入。

关于iphone - 如何从不同线程更改 Core Data 对象,而无需在每次更改后保存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7730648/

相关文章:

java - 在for循环中创建许多不同的线程只产生一个线程(java)

iphone - 如何实现水波纹?

html - 如何使 iFrame 在 iPhone 6 中响应?

java - 是否鼓励在 Java 中使用多处理池?我有正在转换为 Java 的 Python 代码

multithreading - 简单队列模型示例

ios - 在 UITextView 中保存数据

cocoa - 核心数据延迟加载问题

iphone - "No provisioning iOS device connected"

iphone - 如何使用临时变量在 Objective-C/Xcode 中定义返回的 NSString 函数?

ios - 核心数据与 NSFetchedResultsController :Invalid update with with a property in fetchObjects