【问题标题】:Core Data poor performance on save: and [UITableView endUpdates]Core Data 保存时性能不佳:和 [UITableView endUpdates]
【发布时间】:2014-10-07 02:47:23
【问题描述】:

我在使用 Core Data 时遇到了一些性能问题,我希望有人能给我一些关于如何改进它的提示。为我的NSManagedObjectContext 调用save: 时,我遇到单个实体的保存时间超过1 秒。我正在主线程上执行保存,因此在此期间它会锁定 GUI,这是不可接受的。这是我正在执行保存的代码;此代码在修改 WTSRecord 的“描述”字段后启动:

- (void)saveRecord:(WTSRecord *)record
           success:(void (^)(WTSRecord *record))success
           failure:(void (^)(NSError *error))failure {
    DDLogVerbose(@"saveRecord: %@", record.objectID);

    if (record.recordId != nil) {
        //mark this record as a pending modify if it has a database id
        record.pendingModify = [NSNumber numberWithBool:YES];
    }
    NSError *error;
    NSManagedObjectContext *context = record.managedObjectContext;
    NSTimeInterval startTime = [[NSDate date] timeIntervalSince1970];
    if (![context saveToPersistentStore:&error]) {
        failure(error);
    } else {
        NSLog(@"time to persist record: %f", ([[NSDate date] timeIntervalSince1970] - startTime));

        ...do other stuff here...

    }
}

我打开了 SQL 调试,Core Data 只是更新了一条记录,似乎没有做任何不寻常的事情:

2014-08-13 11:25:32.528 Identify[5395:60b] CoreData: sql: BEGIN EXCLUSIVE
2014-08-13 11:25:32.530 Identify[5395:60b] CoreData: sql: UPDATE ZWTSRECORD SET ZDESC = ?, Z_OPT = ?  WHERE Z_PK = ? AND Z_OPT = ?
2014-08-13 11:25:32.531 Identify[5395:60b] CoreData: details: SQLite bind[0] = "ffffffffffffuuuuiiiiuu"
2014-08-13 11:25:32.532 Identify[5395:60b] CoreData: details: SQLite bind[1] = (int64)48
2014-08-13 11:25:32.533 Identify[5395:60b] CoreData: details: SQLite bind[2] = (int64)306
2014-08-13 11:25:32.534 Identify[5395:60b] CoreData: details: SQLite bind[3] = (int64)47
2014-08-13 11:25:32.535 Identify[5395:60b] CoreData: sql: COMMIT
2014-08-13 11:25:32.538 Identify[5395:60b] time to persist record: 1.376321

这似乎是一个非常简单的更新,真的不应该花那么长时间。在这种情况下,sqllite 数据库中大约有 40 个WTSRecords。如果我删除了 sqllite 存储并只修改了一个 WTSRecord,则持久化时间不到 1/20 秒。

我在 cocoanetics 上看到了关于异步保存的 this post,但我只想先知道我是否在做一些根本性错误的事情,然后再走这条路。提前致谢!

编辑 1:

附上时间分析器的屏幕截图。看起来 1.3 秒专门用于在我的 UITableViewController 中运行 controllerDidChangeContent:,这是绘制表格视图单元格。为什么要花这么长时间??

编辑 2

这是我的NSFetchedResultsControllerDelegate 方法。它们并没有真正改变样板 Apple 代码:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    DDLogVerbose(@"FRC calling beginUpdates in controllerWillChangeContent");
    [self.tableView beginUpdates];
    DDLogVerbose(@"FRC done calling beginUpdates in controllerWillChangeContent");
}

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
    switch(type) {
        case NSFetchedResultsChangeInsert:
            DDLogVerbose(@"FRC inserted section");
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            DDLogVerbose(@"FRC deleted section");
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{
    UITableView *tableView = self.tableView;

    switch(type) {
        case NSFetchedResultsChangeInsert:
            DDLogVerbose(@"FRC inserted object");
            [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            DDLogVerbose(@"FRC deleted object");
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeUpdate:
            DDLogVerbose(@"FRC updated object");
            [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
            break;

        case NSFetchedResultsChangeMove:
            DDLogVerbose(@"FRC moved objects");
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    DDLogVerbose(@"FRC calling endUpdates in controllerDidChangeContent");
    [self.tableView endUpdates];
    DDLogVerbose(@"FRC done calling endUpdates in controllerDidChangeContent");
}

我添加了一些打印输出,发现绝大多数时间都花在了[self.tableView endUpdates]。这是我的日志的打印输出:

2014-08-14 10:44:39:663 Identify[5718:60b] Saving to context(<NSManagedObjectContext: 0x145b82c0>)
2014-08-14 10:44:39:666 Identify[5718:60b] FRC calling beginUpdates in controllerWillChangeContent
2014-08-14 10:44:39:666 Identify[5718:60b] FRC done calling beginUpdates in controllerWillChangeContent
2014-08-14 10:44:39:668 Identify[5718:60b] FRC updated object
**2014-08-14 10:44:39:671 Identify[5718:60b] FRC calling endUpdates in controllerDidChangeContent
**2014-08-14 10:44:40:889 Identify[5718:60b] FRC done calling endUpdates in controllerDidChangeContent
2014-08-14 10:44:41:018 Identify[5718:60b] Time to save in context(<NSManagedObjectContext: 0x145b82c0>): 1.355229

【问题讨论】:

  • Instruments Time Profiler 显示什么?这是了解占用时间的第一步。我怀疑这是对保存的 UI 反应。
  • 感谢马库斯的建议。请参阅编辑 1。显然所有时间都花在我的列表视图控制器中绘制/动画表格视图单元格...
  • @Dan 是否要更新题名,添加相关代码等?
  • 添加了 FRC 委托代码和从中的 printlns。所有的时间都花在[UITableView endUpdates]

标签: ios core-data ios7 restkit


【解决方案1】:

好的,我想我想通了。解决方案涉及两部分:

This post 在 stackoverflow 上帮助了我很多。显然,自动布局和屏幕外绘图存在性能问题。我的UITableViewController[tableView endUpdates] 存在性能问题,当FRC 被触发时,它不在屏幕上。我将以下代码添加到 viewWillAppearNSFetchedResultsControllerDelegate 方法。我使用 BOOL 属性来标记何时需要刷新表视图或需要执行获取的结果控制器(我发现需要在删除时发生):

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    if (self.needsFetch) {
        self.needsFetch = NO;
        [self.fetchedResultsController performFetch:nil];
    }
    if (self.needsReload) {
        self.needsReload = NO;
        [self.tableView reloadData];
    }
}

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    //don't update the table view if it isn't being displayed
    if (!self.tableView.window) {
        self.needsReload = YES;
        return;
    }

    DDLogVerbose(@"FRC calling beginUpdates in controllerWillChangeContent.  results list controller type: %i", self.type);
    [self.tableView beginUpdates];
    DDLogVerbose(@"FRC done calling beginUpdates in controllerWillChangeContent");
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{
    //don't update the table view if it isn't being displayed
    if (!self.tableView.window) {
        if (type == NSFetchedResultsChangeDelete) {
            NSLog(@"setting needs fetch");
            self.needsFetch = YES;
        }
        return;
    }

    UITableView *tableView = self.tableView;
    ...same as before...
}


OTHER FRC DELEGATE FUNCTIONS NEED WINDOW NULL CHECK ALSO

我还改进了调用[NSManagedObjectContext saveToPersistentStore] 的方式。该方法是 RestKit 的 NSManagedObjectContext 类别的一部分,在其中我看到他们正在自己的块中执行保存操作并等待,无论 NSManagedObjectContext 是否在它自己的私有队列中运行(RestKit 默认设置父/child 上下文,其中一个写入NSPrivateQueueConcurrencyType 类型的持久存储区)仔细阅读this post 之后,我在自己的NSManagedObjectContext 类别中构建了这个方法,它将通过块回调异步执行核心数据保存:

- (void)saveToPersistentStoreAsync:(void (^)(NSError *error))block {
    [self performBlock:^{
        DDLogVerbose(@"Saving to context(%@)", self);
        NSTimeInterval startTime = [[NSDate date] timeIntervalSince1970];
        NSError *error;
        if (![self save:&error]) {
            if (block) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    block(error);
                });
            }
        } else {
            DDLogVerbose(@"Time to save in context(%@): %f", self, ([[NSDate date] timeIntervalSince1970] - startTime));
            if (self.parentContext) {
                [self.parentContext saveToPersistentStoreAsync:block];
            } else {
                if (block) {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        block(nil);
                    });
                }
            }
        }
    }];
}

总而言之,在这两项更改之后,我的 UI 响应速度更快。我希望我没有做任何会在未来导致某种比赛条件的事情,但希望这对我有用!

【讨论】:

  • 这是一个很好的解决方案,但是由于某种原因,当我实现它时,tableview reloadData 是动画的......你发现同样的事情还是这是我的代码特有的问题?
【解决方案2】:

既然您已经确定您的问题不在于您的保存,而在于您的绘图代码,那么有一些选项。

  1. 深入了解您的手机密码,看看是什么花费了这么长时间。计算什么?绘制 alpha 版?
  2. 将您的存档移出主队列。

选项 2 不能解决您的重绘问题,但会减少您的保存时间。您可以通过在主 MOC 和 PSC 之间放置一个私有 MOC 来做到这一点。然后保存离开主队列。但是您的绘图代码仍然会阻塞。

我会深入每个单元格,看看发生了什么。使用工具打开源代码并找出哪些代码行昂贵,然后探索原因。 Alpha绘图是一个常见问题。图片加载等也很受欢迎。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-23
    • 1970-01-01
    相关资源
    最近更新 更多