核心数据和多线程在保存时崩溃

Posted

技术标签:

【中文标题】核心数据和多线程在保存时崩溃【英文标题】:Core Data and Multithreading crashes on save 【发布时间】:2012-05-08 13:12:43 【问题描述】:

我正在构建一个应用程序,该应用程序从远程服务器提取数据并将它们存储在 CoreData SQLite 数据库中。我正在从后台线程获取数据,而主线程正在使用它。以下是我使用的主要方法。

    所有后台线程都有自己的NSManagedObjectContextpersistentStoreCoordinator 在上下文之间共享。 在后台线程的每次提取中,我都会保存:插入的对象并重置:本地上下文。 我使用通知与主线程上下文进行合并。

我遇到的问题:

随机我在后台线程保存时遇到没有消息的崩溃(NSZombie 和崩溃断点已设置):操作。 正在生成大量重复数据。网络服务数据肯定没问题,所以服务器端没有问题。

这里是代码。

AppDelegate

- (void)applicationDidBecomeActive:(UIApplication *)application

    // Update data from remote server
    WebserviceDataModel *webservice = [[WebserviceDataModel alloc] init];
    webservice.managedObjectContext = self.managedObjectContext;
    [webservice startImport];

后台线程在 WebserviceDataModel 上获取和保存数据

- (void)startImport

    dispatch_queue_t downloadQueue = dispatch_queue_create("startImport in WebserviceDataModel", NULL);
    dispatch_async(downloadQueue, ^
        NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] init];
        moc.persistentStoreCoordinator = self.managedObjectContext.persistentStoreCoordinator;

        // get the remote data
        NSDictionary *lojas = [Loja allLojasFromRemoteServer];

        for (NSDictionary *lojaInfo in lojas) 
            Loja *loja __attribute__((unused)) = [Loja lojaWithRemoteData:lojaInfo inManagedObjectContext:moc];
        

        if ([moc hasChanges]) 
            [moc save:nil];
            [moc reset];
        
        [moc release];        

    );
    dispatch_release(downloadQueue);

用于创建对象的 NSManagedObject 方法:+ (Loja *)lojaWithRemoteData:inManagedContext:

+ (Loja *)lojaWithRemoteData:(NSDictionary *)remoteData inManagedObjectContext:(NSManagedObjectContext *)context

    Loja *loja = nil;

NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:@"Loja" inManagedObjectContext:context];
request.predicate = [NSPredicate predicateWithFormat:@"lojaId = %d", [[remoteData objectForKey:@"lojaId"] intValue]];

NSError *error = nil;
loja = [[context executeFetchRequest:request error:&error] lastObject];
[request release];

if (!error && !loja) 
        // create the record
        loja = [NSEntityDescription insertNewObjectForEntityForName:@"Loja" inManagedObjectContext:context];

        loja.lojaId = [remoteData objectForKey:@"lojaId"];
        loja.email = [remoteData objectForKey:@"email"];
        loja.facebook = [remoteData objectForKey:@"facebook"];
        // ... and others...
    

    return loja;

在 WebserviceDataModel 上订阅 NSManagedObjectContextDidSaveNotification

- (id)init

    self = [super init];
    if (self) 
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:nil];
    
    return self;

WebserviceDataModel 上的 contextChanged: 方法

- (void)contextChanged:(NSNotification*)notification

    if ([notification object] == self.managedObjectContext) return;

    if (![NSThread isMainThread]) 
        [self performSelectorOnMainThread:@selector(contextChanged:) withObject:notification waitUntilDone:YES];
        return;
    
    [[self managedObjectContext] mergeChangesFromContextDidSaveNotification:notification];

【问题讨论】:

好吧,我想通了。它现在就像一个魅力。多线程上下文的实现是正确的。问题出在applicationDidBecomeActive: 我的应用程序使用CoreLocation Fence 来获取功能列表。当应用程序第一次启动时,CoreLocation 框架会向用户显示一条警报消息,指出该应用程序使用用户位置...该警报再次调用applicationDidBecomeActive:,创建两个并发更新波。刚刚将我的 WebserviceDataModel 移动到一个属性并实现了一个标志来知道它是否正在运行。就是这样。 【参考方案1】:

嗯,我想通了。它现在就像一个魅力。多线程上下文的实现是正确的。问题在于applicationDidBecomeActive: 我的应用程序使用CoreLocation Fence 来获取功能列表。当应用程序首次启动时,CoreLocation 框架会向用户显示一条警报消息,指出该应用程序使用用户位置...该警报再次调用applicationDidBecomeActive:,创建两个并发更新波。只需将我的 WebserviceDataModel 移动到一个属性并实现一个标志以了解它是否正在运行。就是这样

只是为了最后的改进,将合并策略更改为NSMergeByPropertyStoreTrumpMergePolicy,所以现在服务器端数据(在内存中)胜过本地存储。

【讨论】:

【参考方案2】:

没有细节就很难理解发生了什么。

说了这么多,首先尝试设置断点,按照应用程序流程。也许你能找到你的应用崩溃的地方。

那么,你确定lojaId 是一个标量值吗?当您创建一个新实体时,您编写了以下内容:

loja.lojaId = [remoteData objectForKey:@"lojaId"];

也许这可能是错误,所以我将尝试以下谓词:

[NSPredicate predicateWithFormat:@"lojaId == %@", [remoteData objectForKey:@"lojaId"]];

最后,当您进行保存时,请尝试记录 NSError 对象。也许您可以找到崩溃的原因。

if ([moc hasChanges]) 

    NSError* error = nil;
    [moc save:&error];

    if(error) 

        NSLog(@"Error during save: %@\n%@", [error localizedDescription], [error userInfo]);
        abort(); // only for debug purposes
    
    [moc reset];

希望对你有帮助。

【讨论】:

loja.lojaId 是一个 NSNumber,所以这就是使用该格式挂载谓词的方式。我已经弄清楚了问题所在。检查问题上的 cmets。谢谢!! @GeorgeVillasboas 好的,没问题。发布您的解决方案作为答案。谢谢。

以上是关于核心数据和多线程在保存时崩溃的主要内容,如果未能解决你的问题,请参考以下文章

实体框架核心和多线程

从后台线程保存数据时核心数据崩溃

GDI+ 和多线程

核心数据和多线程

CoreData 多线程正在生成随机崩溃

多线程和多进程模式有啥区别