0

作業中のアプリで 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 ストアをシードして同じプロセスを実行しました。シードは起動時に正しくロードされますが、シード以降の変更はテーブル ビューにすぐには表示されません。ロードする前に行を追加する必要がある場所までスクロールすると (そこにはありません)、インポートが完了した後でも表示されません。ただし、スクロールして戻ってくると、追加された行が表示されます。データベースが空の場合、スクロールするものが何もないため、何も追加されないようです。シードを使用すると、最終的にそれらが追加されますが、コア データ ストアがアニメーション化された挿入で動作するのを見た方法ではありません。

4

1 に答える 1

1

メイン スレッドのコンテキストがアプリ デリゲートとビュー コントローラーで同じである限り、マージを実行する場所は単なる設計上の決定です。

マージ自体は非常に簡単です。

  1. NSManagedObjectContextDidSave 通知に登録します。
  2. バックグラウンド op を起動します。
  3. バックグラウンド スレッドに保存します。
  4. メイン スレッドのオブザーバー メソッドでコンテキストをマージします。

マージを実行するサンプル コードを次に示します。

// Whatever method you registered as an observer to NSManagedObjectContextDidSave
- (void)contextDidSave:(NSNotification *)notification
{    
       [self.managedObjectContext performSelectorOnMainThread:@selector(mergeChangesFromContextDidSaveNotification:) withObject:notification waitUntilDone:YES];
}

また、通知を起動するには、実際にはバックグラウンド スレッドで保存する必要があることに注意してください。

于 2012-06-25T06:05:25.403 に答える