CoreData子上下文、NSFetchedResultsController和主线程

5

根据CoreData专家Marcus S. Zarra提出的三层CoreData堆栈,我参考了Olivier Drobnik的优秀文章并实现了它:

Three-layer CoreData context architecture

与此图示不同的是,我只使用一个临时后台MOC,以避免在多个临时MOC中插入对象时出现重复。以下是我的上下文初始化代码:

#pragma mark - NSManagedObjectContexts

+ (NSManagedObjectContext *)privateManagedObjectContext
{
    if (!_privateManagedObjectContext) {

        // Setup MOC attached to PSC
        _privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [_privateManagedObjectContext setPersistentStoreCoordinator:[self persistentStoreCoordinator]];

        // Add notification to perform save when the child is updated
        _privateContextSaveObserver =
        [[NSNotificationCenter defaultCenter]
         addObserverForName:NSManagedObjectContextDidSaveNotification
         object:nil
         queue:nil
         usingBlock:^(NSNotification *note) {
             NSManagedObjectContext *savedContext = [note object];
             if (savedContext.parentContext == _privateManagedObjectContext) {
                 [_privateManagedObjectContext performBlock:^{
                     NSLog(@"AMBCoreData -> saving privateMOC");
                     NSError *error;
                     if (![_privateManagedObjectContext save:&error]) {
                         NSLog(@"AMBCoreData -> error saving _privateMOC: %@ %@", [error localizedDescription], [error userInfo]);
                     }
                 }];
             }
         }];
    }
    return _privateManagedObjectContext;
}

+ (NSManagedObjectContext *)mainUIManagedObjectContext
{
    if (!_mainUIManagedObjectContext) {

        // Setup MOC attached to parent privateMOC in main queue
        _mainUIManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
        [_mainUIManagedObjectContext setParentContext:[self privateManagedObjectContext]];

        // Add notification to perform save when the child is updated
        _mainUIContextSaveObserver =
        [[NSNotificationCenter defaultCenter]
         addObserverForName:NSManagedObjectContextDidSaveNotification
         object:nil
         queue:nil
         usingBlock:^(NSNotification *note) {
             NSManagedObjectContext *savedContext = [note object];
             if (savedContext.parentContext == _mainUIManagedObjectContext) {
                 NSLog(@"AMBCoreData -> saving mainUIMOC");
                 [_mainUIManagedObjectContext performBlock:^{
                     NSError *error;
                     if (![_mainUIManagedObjectContext save:&error]) {
                         NSLog(@"AMBCoreData -> error saving mainUIMOC: %@ %@", [error localizedDescription], [error userInfo]);
                     }
                 }];
             }
         }];
    }
    return _mainUIManagedObjectContext;
}

+ (NSManagedObjectContext *)importManagedObjectContext
{
    if (!_importManagedObjectContext) {

        // Setup MOC attached to parent mainUIMOC in private queue
        _importManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
        [_importManagedObjectContext setParentContext:[self mainUIManagedObjectContext]];
    }
    return _importManagedObjectContext;
}

这段代码非常简单。我使用仅在NSMainQueueConcurrencyType中的mainUIManagedObjectContext复制了上述图表。每当子文本importManagedObjectContext得到保存时,通知被触发并且所有父文本都在当前线程中执行保存。
我已经实现了一个测试视图控制器,带有附加的UITableViewNSFetchedResultsController。这是我的测试视图控制器的viewDidLoad中的代码:
- (void)viewDidLoad
{
    [super viewDidLoad];

    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Task"];
    [request setSortDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"insertDate" ascending:NO]]];
    self.fetchRequest = request;
    NSFetchedResultsController *frc =
    [[NSFetchedResultsController alloc]
     initWithFetchRequest:self.fetchRequest
     managedObjectContext:[AMBCoreData mainUIManagedObjectContext]
     sectionNameKeyPath:nil
     cacheName:nil];
    frc.delegate = self;

    [self setFetchedResultsController:frc];
    [self.fetchedResultsController performFetch:nil];
}

在此,我将 mainUIManagedObjectContext 附加到 NSFetchedResultsController 。稍后,在我的 viewDidAppear 中,我运行一个循环以插入几个任务实体:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [[AMBCoreData importManagedObjectContext] performBlock:^{
        for (int i = 0; i < 5000; i++) {
            Task *task = [NSEntityDescription insertNewObjectForEntityForName:@"Task" inManagedObjectContext:[AMBCoreData importManagedObjectContext]];
            task.title = [NSString stringWithFormat:@"Task %d", i];
            task.insertDate = [NSDate new];
        [[AMBCoreData importManagedObjectContext] save:nil];
    }];
}

事情是这样的,我正在插入5000个对象,当数据填充到表视图中时,UI会冻结。 Florian Kugler 使用这种架构进行了一项测试,插入了15000个对象,并通过仪器获得了主线程使用情况(蓝色表示主线程,灰色表示其他线程):

Main thread CPU usage with three-layer context architecture

但是,这是我的主线程CPU使用情况,插入5000个对象时,使用iPhone 5进行分析:

enter image description here

如您所见,我的主线程使用情况远高于Florian的,并且我的UI也会冻结几秒钟。我的问题是,我做错了什么吗?当使用具有NSFetchedResultsControllerUITableView的三层MOC架构时,这是预期的行为吗?我知道插入5000个对象不是大多数应用程序的常规行为,因此当我尝试使用50或100个对象时,冻结不存在或不明显,但是主线程使用率很高(尽管我承认在这种情况下可能是由于其他原因,例如唤醒应用程序)。

最好每隔几百次插入执行一次saveContext,而不是在每次单独插入后执行。顺便说一下,将每个批处理放在@autoreleasepool中以限制内存使用可能会更好。 - Sjors Provoost
1个回答

5

是的,这是预期的,因为主MOC涉及到其子项的保存。当UI上下文的子项不进行大型保存时,这很方便且还算可以接受,但如果这些保存较大,则经常会成为性能问题。使用此模式时,您无法确定UI线程仅执行最小工作。

对于大型保存,我建议创建一个直接配置了持久存储协调器的上下文。在发生大型保存后,您只需重新获取并可选地刷新UI上下文中的数据。有关更多详细信息,请参见我的答案这里


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接