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 组成。

喜欢VC:

- (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 回答