作業中のアプリで Core Data を使用しようとしています。アプリ デリゲートでは、このコードは、ダウンロードした JSON ファイルからのデータのインポートを開始します。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//setup app window, etc.
BECDRefreshOperation *loadData = [[BECDRefreshOperation alloc] initWithStoreCoordinator:self.persistentStoreCoordinator];
[queue addOperation:loadData]; //queue is an NSOperationQueue
return YES;
}
BECDRefreshOperation は、インポートを実行する NSOperation のサブクラスです。メインのオブジェクト コンテキストをバックグラウンド プロセスから分離するために、独自の管理オブジェクト コンテキストを作成します。
- (id) initWithStoreCoordinator:(NSPersistentStoreCoordinator *)storeCoordinator{
if (![super init]) return nil;
[self setPersistentStoreCoord:storeCoordinator];
return self;
}
- (void) main{
NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
[f setNumberStyle:NSNumberFormatterDecimalStyle];
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:self.persistentStoreCoord];
//import the data from JSON
}
実際のインポートが機能し、データ ストアが更新されます。ただし、NSFetchedResultsController を使用するテーブル ビュー コントローラーは更新されません。テーブル ビュー コントローラーは NSFetchedResultsControllerDelegate であり、すべてのデリゲート メソッドが含まれています。
アプリの 2 回目の実行では、データが以前にストアにロードされているため、Table View Controller が正しく表示されます。ただし、インポートで行われた更新は更新されません。
Apple のCore Data Concurrencyガイドラインを何度も読み、Google と SO で何度も検索して答えを見つけました。私はそれがmergeChangesFromContextDidSaveNotificationを使用することにあると信じていますが、保存通知に登録し、変更をマージするメソッドを呼び出すことにより、アプリデリゲートとテーブルビューコントローラーの両方のさまざまな場所でこれを実行しようとしましたが、試したことはありません動作します。Cocoa is my GF's implementationは、これを行うために私が適応しようとした方法の 1 つです。
テーブル ビュー コントローラーは、作成時にアプリ デリゲートの managedObjectContext に渡されます。
マルチスレッドなしでこれを実行しました。データ ストアにインポートしてテーブル ビュー コントローラーに表示するコードは機能しますが、もちろん、データのインポート中に UI がフリーズします。
ここで何か間違ったことをしているのは明らかです。どんな助けでも大歓迎です。
更新 2 つの managedObjectContexts が実際に同じメモリ アドレスを指しているかどうかを確認するために、いくつかの NSLog ステートメントとブレーク ポイントを追加しました。通知コードは機能し、メインの MOC を更新するように見えますが、これまでのところそうではありません。
2012-06-25 21:48:02.669 BE_CoreData[18113:13403] beerListViewController.managedObjectContext = <NSManagedObjectContext: 0x94233d0>
2012-06-25 21:48:02.671 BE_CoreData[18113:13403] appDelegate.managedObjectContext = <NSManagedObjectContext: 0x94233d0>
2012-06-25 21:48:02.722 BE_CoreData[18113:15003] backgroundMOC = <NSManagedObjectContext: 0x7b301b0>
更新 2追加のトラブルシューティングの後、NSFetchedController デリゲート メソッドが起動していないようです。NSFetchedResultsController とそのデリゲートのコードを次に示します。
- (NSFetchedResultsController *)fetchedResultsController {
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:@"Beer" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:@"beertitle" ascending:YES];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"beertitle"
cacheName:@"BeerTable"];
_fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller is about to start sending change notifications, so prepare the table view for updates.
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self configureCell:[self.tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[self.tableView deleteRowsAtIndexPaths:[NSArray
arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths:[NSArray
arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
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)controllerDidChangeContent:(NSFetchedResultsController *)controller {
// The fetch controller has sent all current change notifications, so tell the table view to process all updates.
[self.tableView endUpdates];
}
また、セルの更新が必要なときに呼び出される changeCell のコードは次のとおりです。
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
Beer *beer = [_fetchedResultsController objectAtIndexPath:indexPath];
cell.textLabel.text = beer.beertitle;
if (beer.beerthumb != nil){
[cell.imageView setImageWithURL:[NSURL URLWithString:beer.beerthumb]
placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
}
else {
[cell.imageView setImageWithURL:[NSURL URLWithString:@"http://beersandears.net/images/missing.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
}
}
最後に、viewDidLoad によって fetchBeers メソッドが呼び出され、実際にフェッチが実行されます。
- (void)fetchBeers{
NSError *error;
if (![[self fetchedResultsController] performFetch:&error]) {
// Update to handle the error appropriately.
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
exit(-1); // Fail
}
}
アップデート 3
フェッチが最初に行われることを確認するためのテスト。それはありますが、それほどではありません (これは 4S で実行されました)。
2012-06-28 20:47:37.214 BE_CoreData[3559:907] Fetch called
2012-06-28 20:47:37.281 BE_CoreData[3559:1103] Import started
2012-06-28 20:47:37.285 BE_CoreData[3559:1103] backgroundMOC = <NSManagedObjectContext: 0x1f03f050>
2012-06-28 20:47:39.124 BE_CoreData[3559:1103] call contextDidSave
2012-06-28 20:47:40.926 BE_CoreData[3559:1103] call contextDidSave
2012-06-28 20:47:42.071 BE_CoreData[3559:1103] call contextDidSave
2012-06-28 20:47:45.551 BE_CoreData[3559:1103] call contextDidSave
2012-06-28 20:47:45.554 BE_CoreData[3559:1103] Finished refresh operation
空の SQLlite ストアから始める代わりに、SQLlite ストアをシードして同じプロセスを実行しました。シードは起動時に正しくロードされますが、シード以降の変更はテーブル ビューにすぐには表示されません。ロードする前に行を追加する必要がある場所までスクロールすると (そこにはありません)、インポートが完了した後でも表示されません。ただし、スクロールして戻ってくると、追加された行が表示されます。データベースが空の場合、スクロールするものが何もないため、何も追加されないようです。シードを使用すると、最終的にそれらが追加されますが、コア データ ストアがアニメーション化された挿入で動作するのを見た方法ではありません。