うな(。・ε・。)

Android, iOS, AppEngine まわりのめもめも

MagicalRecord で非同期クエリを行う

MagicalRecord の + MR_findAll などの同期メソッドは非常に便利なのですが、UI を非常にブロックしやすい性質があります。大量のオブジェクトが保存されうる場合に特に注意が必要です。 めやすとしては、1000 件以上のデータは同期メソッドでは顕著になります。

特に、- viewDidLoad でクエリを行うと、画面遷移が起こる前に UI がブロックされます。結果、「タップしたのになかなか移動しない」イライラ感に繋がります。

非同期にクエリを行う

非同期にクエリを行うには、NSManagedContextNSFetchRequest を直接取り扱う必要があります。

// XXXManager.m

- (void)fetchAllXXXWithPredicate:(NSPredicate *)predicate completionBlock:(void(^)(NSArray *fetchResults))completionBlock {
    NSFetchRequest *request = [XXX MR_createFetchRequest];
    request.predicate = predicate;
    
    NSManagedObjectContext *context = [NSManagedObjectContext MR_contextForCurrentThread];
    [context performBlock:^{
        NSArray *result = [context executeFetchRequest:request error:nil];

        completionBlock(result);
    }];
}

NSFetchedResultsController で非同期にクエリを行う

NSFetchedResultsController を使う場合は、利用する ViewController 内で NSFetchedResultsController を初期化してから、以下のようにします。(エラー処理は簡略化してます)

@weakify(self);
[self.fetchedResultsController.managedObjectContext performBlock:^{
    @strongify(self);
    [self.fetchedResultsController performFetch:nil];

    @weakify(self);
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        @strongify(self);
        [self.collectionView reloadData];
    }];
}];

これは NSFetchedResultsController のカテゴリを作ってもよいでしょう。

// NSFetchedResultsController+AsyncFetch.m

- (void)ki_performFetchWithCompletionBlock:(void(^)(NSError *errorOrNil))completionBlock {
    @weakify(self);
    [self.managedObjectContext performBlock:^{
        @strongify(self);
        NSError *errorOrNil = nil;
        [self performFetch:&errorOrNil];
    
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            completionBlock(errorOrNil);
        }];
    }];
}

参考文献