2

私はNSFetchedResultsControllerによって駆動されるUITableViewを持っています。テーブルビューにはカスタム フッター ビューがあり、RestKit を使用して API からデータを取得し、応答をコア データにマップしています。

RestKit リクエストが返された後、レスポンスがコア データにマップされ、FetchedResultsController がそのデリゲート (テーブルビュー コントローラー) の更新を送信し、テーブルビューが部分的に更新されるという非常に奇妙な動作がすべて 1 秒未満で発生します。その後、数秒後、テーブルビューの更新が終了します。最初はテーブルフッタービューが再描画されず、その下にセルが追加されるため、テーブルビューが部分的に更新されると言います。フッターが所定の位置に移動するまで、遅延はありません。

計測器を使用してアプリのプロファイリングを試みましたが、これはセルの描画/読み込みにおける CPU ベースの遅延ではないようです。また、複雑さを最小限に抑えるためにセルを構成するときにのみ、セルのタイトル ラベルを設定することも試みました。また、デバイスとシミュレーターの両方で同じ動作が見られます。

この遅延はユーザー エクスペリエンスの低下の原因となるため、追跡して解消したいと考えています。

ログからの例外:

2012-09-18 10:39:43.695 MyApp[28881:470f] -[RDRMarketBooksViewController controllerWillChangeContent:] [Line 236] controller => <NSFetchedResultsController: 0xe657570>
2012-09-18 10:39:43.695 MyApp[28881:470f] -[RDRMarketBooksViewController controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:] [Line 267] controller => <NSFetchedResultsController: 0xe657570>
(...19 similiar lines...)
2012-09-18 10:39:43.703 MyApp[28881:470f] -[RDRMarketBooksViewController controllerDidChangeContent:] [Line 301] controller => <NSFetchedResultsController: 0xe657570>
2012-09-18 10:39:43.705 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0xed5f890; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0xed5f9b0>>, indexPath => <NSIndexPath 0xed5eab0> 2 indexes [0, 0]
2012-09-18 10:39:43.706 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0xe659e50; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0xe659f70>>, indexPath => <NSIndexPath 0xe657b50> 2 indexes [0, 1]
2012-09-18 10:39:43.708 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0xe65aca0; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0xe65adc0>>, indexPath => <NSIndexPath 0xe65a6f0> 2 indexes [0, 2]
2012-09-18 10:39:43.709 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0xe9e1890; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0xe9b1e10>>, indexPath => <NSIndexPath 0xe9c59d0> 2 indexes [0, 3]
2012-09-18 10:39:43.711 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0x13c6f230; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0x13c6ed80>>, indexPath => <NSIndexPath 0xe9e0fb0> 2 indexes [0, 4]
2012-09-18 10:39:43.712 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0xe9e2230; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0xe9b1a60>>, indexPath => <NSIndexPath 0xe9e0ea0> 2 indexes [0, 5]
2012-09-18 10:39:43.713 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0x13c6fef0; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0x13c70010>>, indexPath => <NSIndexPath 0xe95dca0> 2 indexes [0, 6]
2012-09-18 10:39:43.714 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0xeb599f0; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0xeb59b10>>, indexPath => <NSIndexPath 0xe9e2a10> 2 indexes [0, 7]
2012-09-18 10:39:43.715 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0x13c70d10; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0x13c6fea0>>, indexPath => <NSIndexPath 0xe9e1790> 2 indexes [0, 8]
2012-09-18 10:39:43.717 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0xed616c0; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0xed5ee80>>, indexPath => <NSIndexPath 0xe9e1d50> 2 indexes [0, 9]
2012-09-18 10:39:43.718 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0xe65bb00; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0xe65b680>>, indexPath => <NSIndexPath 0xe65b1c0> 2 indexes [0, 11]
2012-09-18 10:39:43.720 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0x13c71c00; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0x13c71770>>, indexPath => <NSIndexPath 0x13c6f1d0> 2 indexes [0, 13]
2012-09-18 10:39:43.721 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0xe65c900; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0xe65c4a0>>, indexPath => <NSIndexPath 0xe659ce0> 2 indexes [0, 14]
2012-09-18 10:39:43.722 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0xe65d720; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0xe65d2c0>>, indexPath => <NSIndexPath 0xe659de0> 2 indexes [0, 15]
2012-09-18 10:39:43.723 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0xe65e5a0; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0xe65e110>>, indexPath => <NSIndexPath 0xe65d2a0> 2 indexes [0, 16]
2012-09-18 10:39:43.739 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0x91bf9e0; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0x9197560>>, indexPath => <NSIndexPath 0x91c9960> 2 indexes [0, 17]
2012-09-18 10:39:43.740 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0x93e8d50; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0x93dd090>>, indexPath => <NSIndexPath 0x93cb8b0> 2 indexes [0, 18]
2012-09-18 10:39:43.741 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0x13c727b0; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0x13c728d0>>, indexPath => <NSIndexPath 0x13c72420> 2 indexes [0, 19]
2012-09-18 10:39:43.742 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0x93e6ff0; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0x93da000>>, indexPath => <NSIndexPath 0x93d4d10> 2 indexes [0, 21]
2012-09-18 10:39:43.744 MyApp[28881:470f] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0xed62510; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0xed620a0>>, indexPath => <NSIndexPath 0xed5ed80> 2 indexes [0, 22]
2012-09-18 10:39:43.772 MyApp[28881:c07] -[RDRMarketBooksViewController controllerWillChangeContent:] [Line 236] controller => <NSFetchedResultsController: 0xe657570>
2012-09-18 10:39:43.777 MyApp[28881:c07] -[RDRMarketBooksViewController controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:] [Line 267] controller => <NSFetchedResultsController: 0xe657570>
(...19 similiar lines...)
2012-09-18 10:39:43.788 MyApp[28881:c07] -[RDRMarketBooksViewController controllerDidChangeContent:] [Line 301] controller => <NSFetchedResultsController: 0xe657570>
2012-09-18 10:39:43.790 MyApp[28881:c07] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0x13c7be10; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0x13c7bf30>>, indexPath => <NSIndexPath 0x13c7b080> 2 indexes [0, 0]
2012-09-18 10:39:43.791 MyApp[28881:c07] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0xe9e2be0; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0xe9e1b10>>, indexPath => <NSIndexPath 0xe977940> 2 indexes [0, 1]
2012-09-18 10:39:43.792 MyApp[28881:c07] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0xe9e38b0; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0xe9e2190>>, indexPath => <NSIndexPath 0xe9c0bc0> 2 indexes [0, 2]
2012-09-18 10:39:43.793 MyApp[28881:c07] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0xe9e4680; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0xe9e41f0>>, indexPath => <NSIndexPath 0xe9e3d60> 2 indexes [0, 3]
2012-09-18 10:39:43.794 MyApp[28881:c07] -[RDRMarketBooksViewController configureCell:atIndexPath:] [Line 34] cell => <RDRMarketBooksCell: 0x13c7ccc0; baseClass = UITableViewCell; frame = (0 0; 320 79); autoresize = W+BM; layer = <CALayer: 0x13c7cde0>>, indexPath => <NSIndexPath 0x13c7b270> 2 indexes [0, 4]
2012-09-18 10:39:43.802 MyApp[28881:c07] -[RDRMarketBooksViewController productsDidLoad:forCategory:error:] [Line 319] resource => <RDRRangedResource: 0x9533390> => {

そして、私の UITableViewController サブクラスからの関連するメソッド:

- (void)configureCell:(RDRMarketBooksCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    NSParameterAssert(cell);
    NSParameterAssert(indexPath);

    Trace(@"cell => %@, indexPath => %@", cell, indexPath);

    NSAssert([cell isKindOfClass:[RDRMarketBooksCell class]], @"should be a market books cell");
    id<RDRAPIProduct> product = [fetchedResultsController objectAtIndexPath:indexPath];
    cell.titleLabel.text = product.name;
}

#pragma mark -
#pragma mark UITableViewDelegate

// Configuring Rows for the Table View

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 79.0;
}

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (!tableViewIsUpdating) {
        [(RDRMarketBooksCell *)cell refreshSecondaryValues];
    }
}

// Managing Selections

- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    id<RDRAPIProduct> product = [fetchedResultsController objectAtIndexPath:indexPath];

    BOOL shouldSelect = YES;
    if (product) {
        shouldSelect = (product.slug != nil);
    }
    return (shouldSelect ? indexPath : nil);
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    id<RDRAPIProduct> product = [fetchedResultsController objectAtIndexPath:indexPath];

    if (RDRIsiPad()) {
        [[RDRApplicationDelegate sharedDelegate] presentDetailsWithProduct:product];
    }

    [self.delegate viewController:self didPickProduct:product];

    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

#pragma mark -
#pragma mark UITableViewDataSource

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    RDRMarketBooksCell *cell = [tableView dequeueReusableCellWithIdentifier:kRDRMarketBooksCellIdentifier];
    NSAssert(cell, @"cell should not be nil");

    [self configureCell:cell atIndexPath:indexPath];

    return cell;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [[fetchedResultsController sections] count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo numberOfObjects];
}

#pragma mark -
#pragma mark NSFetchedResultsControllerDelegate

/*
 Assume self has a property 'tableView' -- as is the case for an instance of a UITableViewController
 subclass -- and a method configureCell:atIndexPath: which updates the contents of a given cell
 with information from a managed object at the given index path in the fetched results controller.
 */

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    Trace(@"controller => %@", controller);
    if (controller == fetchedResultsController) {
        tableViewIsUpdating = YES;
        [self.tableView beginUpdates];
    }
}


- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
    Trace(@"controller => %@", controller);

    if (controller == fetchedResultsController) {
        switch(type) {
            case NSFetchedResultsChangeInsert:
                [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                              withRowAnimation:UITableViewRowAnimationFade];
                break;

            case NSFetchedResultsChangeDelete:
                [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 {
    Trace(@"controller => %@", controller);

    if (controller == fetchedResultsController) {
        UITableView *tableView = self.tableView;

        switch(type) {

            case NSFetchedResultsChangeInsert:
                [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                                 withRowAnimation:UITableViewRowAnimationFade];
                break;

            case NSFetchedResultsChangeDelete:
                [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                                 withRowAnimation:UITableViewRowAnimationFade];
                break;

            case NSFetchedResultsChangeUpdate:
                [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                                 withRowAnimation:UITableViewRowAnimationFade];
                break;

            case NSFetchedResultsChangeMove:
                [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                                 withRowAnimation:UITableViewRowAnimationFade];
                [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                                 withRowAnimation:UITableViewRowAnimationFade];
                break;
        }
    }
}


- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    Trace(@"controller => %@", controller);
    if (controller == fetchedResultsController) {
        [self.tableView endUpdates];
        tableViewIsUpdating = NO;

//        double delayInSeconds = 0.1;
//        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
//        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
//            [self fetchSecondaryValues];
//        });
    }
}
4

1 に答える 1

1

解決しました。NSFetchedResultsController は、RKManagedObjectStore の primaryManagedObjectContext を使用していました。mainQueueManagedObjectContext に切り替えたところ、問題は解決しました。

RestKit を詳しく調べたところ、mainQueueManagedObjectContext は primaryManagedObjectContext の子です (そして、この事実がドキュメントに記載されていることがわかりました)。私は当初、RestKit マッピングは primaryManagedObjectContext で発生すると考えていましたが、今では mainQueueManagedObjectContext で発生し、優先順位の低いスレッドで primaryManagedObjectContext に保存され、次に永続ストアに保存されると推測しています。私の NSFetchedResultsController が以前に primaryManagedObjectContext にアタッチされていたため、結果が完全に表示されるまでの長い遅延が説明できました。ただし、この場合に mainQueueManagedObjectContext を使用する必要性は、ドキュメントでは明らかではありませんでした。しかし、振り返ってみると、

NSFetchedResultsController を作成するコードは次のようになります。

    NSManagedObjectContext *context = [[RKManagedObjectStore defaultStore] mainQueueManagedObjectContext];
    NSFetchRequest *fr = [NSFetchRequest fetchRequestWithEntityName:@"RDRProduct"];
    fr.fetchBatchSize = 20;
    fr.predicate = [NSPredicate predicateWithFormat:@"ANY categories.slug == %@", category.slug];
    fr.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"sortingTitle" ascending:YES]];
    [NSFetchedResultsController deleteCacheWithName:category.slug];
    NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fr
                                                                                               managedObjectContext:context
                                                                                                 sectionNameKeyPath:@"sortingSection"
                                                                                                          cacheName:category.slug];

私のテーブルビューは正しく動作するようになりました。

于 2012-09-19T07:42:54.893 に答える