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

时间:2012-05-08 13:12:44

标签: objective-c multithreading core-data grand-central-dispatch

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

  1. 所有后台主题都有自己的NSManagedObjectContext
  2. persistentStoreCoordinator在上下文之间共享。
  3. 在后台线程上的每次获取时,我保存:插入的对象并重置:本地上下文。
  4. 使用通知我与主线程上下文合并。
  5. 我遇到的问题:

    • 随机地我在后台线程保存:操作时没有消息(NSZombie和Crash Breakpoints设置)崩溃。
    • 生成了大量重复数据。 Web服务数据肯定没问题,所以服务器端没问题。

    这是代码。

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

2 个答案:

答案 0 :(得分:1)

好吧,我已经弄明白了。它现在就像一个魅力。多线程上下文的实现是正确的。问题出在applicationDidBecomeActive:我的应用程序使用CoreLocation Fence获取功能列表。当应用程序首次启动时,CoreLocation框架会向用户显示一条警告消息,指出应用程序使用de user位置...该警报再次调用applicationDidBecomeActive:,创建两个并发的更新波。刚刚将我的WebserviceDataModel移动到一个属性并实现了一个标志,以了解它是否正在运行。多数民众赞成

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

答案 1 :(得分:0)

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

说,首先,尝试设置断点并遵循应用程序流程。也许你可以找到应用程序崩溃的地方。

然后,您确定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];
}

希望它有所帮助。