MagicalRecord で非同期クエリを行う
MagicalRecord の + MR_findAll
などの同期メソッドは非常に便利なのですが、UI を非常にブロックしやすい性質があります。大量のオブジェクトが保存されうる場合に特に注意が必要です。
めやすとしては、1000 件以上のデータは同期メソッドでは顕著になります。
特に、- viewDidLoad
でクエリを行うと、画面遷移が起こる前に UI がブロックされます。結果、「タップしたのになかなか移動しない」イライラ感に繋がります。
非同期にクエリを行う
非同期にクエリを行うには、NSManagedContext
と NSFetchRequest
を直接取り扱う必要があります。
// 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); }]; }]; }