1

ユーザーが写真を見たとき、ボタンを押すことで気に入ることができます。次のコードが実行されます。

- (void)feedback:(Item *)item isLiked:(bool)liked {
    // Update the item with the new score asynchornously
    NSManagedObjectID *itemId = item.objectID;
    dispatch_async(dispatch_get_main_queue(), ^{
        // Create a new managed object context and set its persistent store coordinator
        // Note that this **must** be done here because this context belongs to another thread
        AppDelegate *theDelegate = [[UIApplication sharedApplication] delegate];
        NSManagedObjectContext *localContext = [[NSManagedObjectContext alloc] init];
        [localContext setPersistentStoreCoordinator:[theDelegate persistentStoreCoordinator]];

        Item *localItem = (Item *)[localContext objectWithID:itemId];
        localItem.liked = [NSNumber numberWithBool:liked];
        localItem.updated_at = [NSDate date];
        NSError *error;
        if (![localContext save:&error]) {
            NSLog(@"Error saving: %@", [error localizedDescription]);
        }
    });

私のアプリでは、LikedViewController は、ユーザーが気に入った画像を表示します。LikedVC は、NSFetchedResultsController に接続された UITableViewController で構成されます。

LikedVC:

- (void)viewDidLoad
{
    [super viewDidLoad];

    // NSFetchedResultsController
    NSManagedObjectContext *moc = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
    _fetchedResultsController = \
    [[NSFetchedResultsController alloc] initWithFetchRequest:[self.delegate getFetchRequest]
                                        managedObjectContext:moc
                                          sectionNameKeyPath:nil
                                                   cacheName:nil];  // TODO investigate whether we should bother with cache
    _fetchedResultsController.delegate = self;

    NSError *error;
    if (![[self fetchedResultsController] performFetch:&error]) {
        // Update to handle the error appropriately.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        exit(-1);  // Fail
    }

    // Bottom loading bar
    self.tableView.tableFooterView = self.footerView;
    self.footerActivityIndicator.hidesWhenStopped = true;

    // ActivityIndicator
    self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
    self.activityIndicator.color = [UIColor blackColor];
    [self.tableView addSubview:self.activityIndicator];
    self.activityIndicator.hidesWhenStopped = true;
    // FIXME Unable to center it inside the tableView properly
    self.activityIndicator.center = CGPointMake(self.tableView.center.x, self.tableView.center.y - self.tabBarController.tabBar.frame.size.height);
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    // Automatically fetch when there is nothing in the UITableView
    if ([self tableView:self.tableView numberOfRowsInSection:0] == 0) {
        if ([self canFetch]) {
            [self refill];
        }
    }
}

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];

    if (self.operation && self.operation.isExecuting) {
        NSLog(@"Cancelling Operation: %@", self.operation);
        [self.operation cancel];
        self.isFetching = false;
    }
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

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

    static NSString *CellIdentifier = @"FeedCell";
    Item *item = [_fetchedResultsController objectAtIndexPath:indexPath];

    FeedCell *cell = (FeedCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    cell.item = item;
    cell.tag = indexPath.row;
    cell.customImageView.userInteractionEnabled = YES;

    // NOTE Don't try to do this at the UITableViewCell level since the tap will be eaten by the UITableView/ScrollView
    if (self.likeOnTap) {
        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapped:)];
        tap.numberOfTapsRequired = 1;
        [cell.customImageView addGestureRecognizer:tap];
    }

    // Set up the buttons
    [cell.likeButton addTarget:self action:@selector(liked:) forControlEvents:UIControlEventTouchUpInside];
    [cell.dislikeButton addTarget:self action:@selector(disliked:) forControlEvents:UIControlEventTouchUpInside];
    [cell.detailButton addTarget:self action:@selector(detailed:) forControlEvents:UIControlEventTouchUpInside];

    [[SDImageCache sharedImageCache] queryDiskCacheForKey:item.image_url done:^(UIImage *image, SDImageCacheType type) {
        if (image) {
            [cell setCustomImage:image];
        } else {
            // If we have to download, make sure user is on the image for more than 0.25s before we
            // try to fetch. This prevents mass downloading when the user is scrolling really fast
            double delayInSeconds = 0.25;
            dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
            dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
                if ([self isIndexPathVisible:indexPath]) {
                    [SDWebImageDownloader.sharedDownloader
                     downloadImageWithURL:[NSURL URLWithString:item.image_url]
                     options:0
                     progress:^(NSUInteger receivedSize, long long expectedSize) { }
                     completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
                         if (image && finished) {
                             [cell setCustomImage:image];
                             [[SDImageCache sharedImageCache] storeImage:image forKey:item.image_url];
                         }
                     }];
                }
            });
        }
    }];

    // Check if we are almost at the end of the scroll. If so, start fetching.
    // Doing this here is better than overriding scrollViewDidEndDragging
    if (indexPath.row >= [self.tableView numberOfRowsInSection:0] - 3) {
        [self refill];
    }

    return cell;
}

#pragma mark - Table view delegate

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    id sectionInfo = [_fetchedResultsController.sections objectAtIndex:section];
    NSInteger ret = [sectionInfo numberOfObjects];
    self.hasContent = (ret != 0);
    return ret;
}


# pragma mark - NSFetchedResultsControllerDelegate

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    NSLog(@"1");
    [self.tableView beginUpdates];
}

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
    NSLog(@"2");
    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 {
    NSLog(@"3");
    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:
            [NSException raise:@"Unknown update" format:@"NSFetchedResultsChangeUpdate: invoked"];
            // [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
            break;

        case NSFetchedResultsChangeMove:
            [NSException raise:@"Unknown update" format:@"NSFetchedResultsChangeMove: invoked"];
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    NSLog(@"4");
    [self.tableView endUpdates];
}

(この質問を短くするために、一部の情報を省略していることに注意してください)

LikedVCのfetchRequestです

- (NSFetchRequest *)getFetchRequest {
    NSManagedObjectContext *moc = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Item" inManagedObjectContext:moc];
    [request setPredicate:[NSPredicate predicateWithFormat:@"liked == %d OR origin == %d", 1, OriginsLike]];
    [request setEntity:entity];
    [request setResultType:NSManagedObjectResultType];
    [request setFetchBatchSize:10];
    NSSortDescriptor *d = [[NSSortDescriptor alloc] initWithKey:@"updated_at" ascending:NO selector:nil];
    [request setSortDescriptors:[NSArray arrayWithObject:d]];
    return request;
}

ユーザーがアイテムを気に入ったのに、ユーザーが LikedVC に切り替えるとアイテムがどこにも表示されないというバグが見られます。

tableView の controllerDidChangeContent、controllerWillChangeContent などのメソッドに NSLog(@"1")、NSLog(@"2")、... を追加しました。「1」、「2」、.. がまったくログに記録されていません。

NSFetchedResultsController が機能しないのはなぜですか?

4

1 に答える 1

0

直接回答

あなたの質問に対する直接的な答えは NSManagedObjectContextDidSaveNotification、一時的なコンテキストからの変更を観察し、観察しているメインのコンテキストにマージする必要があるというNSFetchedResultsControllerことです。メインコンテキストを所有するオブジェクトでこれを行うのが最善です。アプリデリゲートを使用しているようです。

次のようにデータをマージしました。

- (void)managedObjectContextDidSave:(NSNotification *)notification
{
    // if the notification is on a background thread, forward it to the main thread
    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(managedObjectContextDidSave:) withObject:notification];
        return;
    }

    // if a context other than the main context has saved, merge the changes
    if (notification.object != self.managedObjectContext) {
        [self.managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
    }
}

あなたのコードは疑わしいようです

すべてのロジックはメイン キューで発生するため、別のコンテキストはまったく必要ありません。dispatch_asyncこの場合、メイン キューで使用するメリットもありません。メインスレッドにすべてを保持する場合は、新しいオブジェクトを作成してメインコンテキストに直接保存します。本当に非同期にしたい場合は、dispatch_async を使用してブロックをバックグラウンド キューにディスパッチし、バックグラウンド スレッド用に (実装したように) 新しいコンテキストが必要になります。

于 2013-04-11T01:56:45.010 に答える