核心数据背景可以节省性能问题

时间:2014-03-18 14:14:00

标签: ios objective-c multithreading core-data

我使用核心数据编写了一个应用程序,该数据通过wifi从传感器收集各种数据。我的数据线程在后台运行,从wifi读取数据,并创建新的核心数据实体。问题是我每秒钟会获得大约27次更新,当我尝试让线程在接收到对象时立即调用保存对象时,我的UI开始滞后,程序变得无法使用我&# 39;我不确定这是由于我的代码中的设计缺陷还是与Core Data的工作原理有关。

我想知道如何在不影响我的用户界面或任何其他应用程序代码的情况下主动进行后台保存的一些选项。我原本以为有一种方法可以在另一个后台线程或其他东西中每隔几秒发出一次500条记录的批量保存,但我不确定a)如何实现这一点和b)如果可能的话。

我通过以下方式主动创建对象:

[NSEntityDescription insertNewObjectForEntityForName:@"RPYL" inManagedObjectContext:managedObjectContext];

一旦我完成了数据收集,我就打电话给:

[managedObjectContext save:&error]

2 个答案:

答案 0 :(得分:3)

你无法在后台保存而对主线程没有影响 - SQLite开发人员对"Threads are evil. Avoid them."有过时的看法。因此,使用SQLite涉及很多互斥。在保存时,无论保存源自何处,持久存储都会被锁定。如果在此期间需要访问商店,则必须等待。如果您有受影响的索引,则保存涉及获取受影响的表中的每个项目,并根据索引列对它们进行排序,因为SQLite通过二进制搜索实现索引。因此,成本可以取决于您的插入量以及您已经在商店中拥有的数量。

首先尝试在线程限制跳转到主队列时出现智能错误 - 使用NSFetchRequest并明确表示您希望事情发生故障。然后您就可以访问它们而无需再进入商店,从而避免您使用互斥锁。尝试使用-com.apple.CoreData.SQLDebug 1为您的目标设置运行,以了解您去商店的频率:如果它不是您已优化的东西,那么我保证它会是一个巨大的比你想象的要多。

一般提示也适用:针对runloop计划,而不是直接进入主队列,如果您有工作要做而不是在用户与应用程序交互时。 runloop进入和退出跟踪模式,因此您可以将工作安排为默认模式,并且当UI忙于用户控制时,它将自动避免运行。

如果这不能解决问题,并且您的记录在收到时不会有任何复杂的查询,那么请考虑使用Core Data作为永久存储,但是为运行时使用自己设计的不可变非托管对象。在后台构建它们并将它们向前传递。

编辑:批量保存很容易 - 实际上不要太频繁地保存。一种解决方案是使BOOL指示是否安排了保存;当你想要保存时,如果没有安排,请从现在开始用dispatch_after安排一秒钟。如果安排一个,那么什么也不做。如果您的数据不断进入,那么更复杂的方案(例如,我上次保存的时间,那么我可以再保存多长时间?)并不会让您获益匪浅。

答案 1 :(得分:1)

您需要注意一些事项,并采取一些措施来提高效果。

<强>设置: 我使用DataController类来设置并提供对所有Core Data对象(persistentStoreCoordinator等)的访问。在该类中,我有一个类方法,用于在后台运行Core Data操作。它创建了一个新的NSManagedObjectContext(因为我们永远不应该在线程之间传递NSManagedObjectContext)并且在一个新的上下文中执行一个块。该方法如下所示:

+ (void)saveDataInBackgroundWithSaveBlock:(void(^)(NSManagedObjectContext *context))saveBlock   
                          completionBlock:(void(^)(void))completionBlock
{     
    // nested contexts are broken on iOS 5, see http://stackoverflow.com/questions/11786436/core-data-nested-managed-object-contexts-and-frequent-deadlocks-freezes
    // that's why we directly use the persistentStoreCoordinator instead

    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    // setting the undoManager to nil dramatically improves performance and memory usage
    context.undoManager = nil;
    [context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
    [context setPersistentStoreCoordinator:[[self class] sharedInstance].persistentStoreCoordinator];        

    // make sure our changes are merged into our main `NSManagedObjectContext`
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    [notificationCenter addObserver:[[[self class] sharedInstance] managedObjectContext]
                           selector:@selector(mergeChangesFromContextDidSaveNotification:)
                               name:NSManagedObjectContextDidSaveNotification
                             object:context];

    [context performBlock:^{
        saveBlock(context);
        if ([context hasChanges])
        {
            NSError *error;
            if ([context save:&error]) {
                NSLog(@"saving in bg successful");
            } else {
                NSLog(@"Error bg save: %@", error);
            }
        }

        dispatch_async(dispatch_get_main_queue(), ^{
            completionBlock();
        });
    }];
}

然后你可以这样调用这个方法:

[DataController saveDataInBackgroundWithSaveBlock:^(NSManagedObjectContext *context) {
    // create NSManagedObjects is the background with the given context
} completionBlock:^{
    // do something on the main thread, for example
    // [self.collectionView reloadData];
}];

<强>性能: 设置context.undoManager = nil;对内存使用和性能有很大帮助。此外,请不要在创建的每个对象后保存。相反,定期保存,这也会有所帮助。

很多信用归功于http://www.cimgf.com/2011/05/04/core-data-and-threads-without-the-headache/