为什么在删除对象时不触发NSFetchedResultsChangeDelete?

时间:2012-05-18 07:32:42

标签: objective-c ios xcode uitableview nsfetchedresultscontroller

插入(NSFetchedResultsChangeInsert)正在我的后台线程,来自同一个线程,使用相同的managedObjectContext保存 - 合并通知,因此得到正确保存,删除不起作用(在tableviews中可见删除动画因此不工作)。

已更新,现在显示有问题的SchedulesViewController.m的所有代码:

#import "SchedulesViewController.h"
#import "CustomScheduleTableViewCell.h"
#import <QuartzCore/QuartzCore.h>
#import "ViewScheduleViewController.h"

@implementation SchedulesViewController {
    NSIndexPath *deleteActionIndexPath;
}

@synthesize fetchedResultsController, managedObjectContext;

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)didReceiveMemoryWarning
{
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in use.
}

#pragma mark - View lifecycle

- (void)viewDidLoad
{
    [super viewDidLoad];

    if (managedObjectContext == nil) { 
        managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext]; 
    }

    self.title = @"Schema's";

    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    self.navigationItem.leftBarButtonItem = self.editButtonItem;

    NSError *error = nil;
    if (![[self fetchedResultsController] performFetch:&error]) {
        /*
         Replace this implementation with code to handle the error appropriately.

         abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
         */
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
}

- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
    //storedReminderSchedules = nil;
    //context = nil;
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    NSLog(@"Schedules viewWillAppear");
}

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

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

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

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    return YES;
}

#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return [[fetchedResultsController sections] count];
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    // check if we really have any sections in the managed object:
    //if (!fetchedResultsController.sections.count) return @"Persoonlijk";

    id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo name];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // check if we really have any sections in the managed object:
    //if (!fetchedResultsController.sections.count) return 0;

    id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo numberOfObjects];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"ScheduleCell";
    CustomScheduleTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    [self configureCell:cell atIndexPath:indexPath];
    return cell;
}


// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
    return YES;
}

- (void)configureCell:(CustomScheduleTableViewCell*)cell atIndexPath:(NSIndexPath*)indexPath {
    // Configure the cell.
    ReminderSchedule *reminderSchedule = [fetchedResultsController objectAtIndexPath:indexPath];
    cell.name.text = reminderSchedule.name;
}

#pragma mark - Table view delegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if ([[[[fetchedResultsController sections] objectAtIndex:indexPath.section] name] hasPrefix:@"Dr."]) {
        ViewScheduleViewController *controller = [[ViewScheduleViewController alloc]init];
        controller.reminderSchedule = [self.fetchedResultsController objectAtIndexPath:[self.tableView indexPathForSelectedRow]];
        controller.context = [fetchedResultsController managedObjectContext];
        [self.navigationController pushViewController:controller animated:YES];
    }
    else {
        [self performSegueWithIdentifier:@"EditSchedule" sender:self];
    }
}

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
    return UITableViewCellEditingStyleDelete;
}

#pragma mark - ModifyScheduleViewControllerDelegate
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([segue.identifier isEqualToString:@"AddSchedule"])
    {
        [TestFlight passCheckpoint:@"Add Schedule"];
        // Get reference to the destination view controller
        UINavigationController *navigationController = segue.destinationViewController;
        ModifyScheduleViewController *controller = [[navigationController viewControllers] objectAtIndex:0];
        controller.delegate = self;
        controller.fetchedResultsController = self.fetchedResultsController;
        [controller.navigationItem.rightBarButtonItem setEnabled:NO];
    }
    else if ([segue.identifier isEqualToString:@"EditSchedule"]) {
        [TestFlight passCheckpoint:@"Edit Schedule"];
        // Get reference to the destination view controller
        UINavigationController *navigationController = segue.destinationViewController;
        ModifyScheduleViewController *controller = [[navigationController viewControllers] objectAtIndex:0];
        controller.delegate = self;
        controller.fetchedResultsController = self.fetchedResultsController;
        controller.reminderSchedule = [self.fetchedResultsController objectAtIndexPath:[self.tableView indexPathForSelectedRow]];
    } 
}

#pragma mark -
#pragma mark Fetched results controller

- (NSFetchedResultsController *)fetchedResultsController {

    if (fetchedResultsController != nil) {
        return fetchedResultsController;
    }

    /*
     Set up the fetched results controller.
     */
    // Create the fetch request for the entity.
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"ReminderSchedule" inManagedObjectContext:managedObjectContext];
    [fetchRequest setEntity:entity];

    // Set the batch size to a suitable number.
    [fetchRequest setFetchBatchSize:20];

    // Edit the sort key as appropriate.
    NSSortDescriptor *sortDescriptorName = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:NO];
    NSSortDescriptor *sortDescriptorSection = [[NSSortDescriptor alloc] initWithKey:@"sectionName" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptorSection,sortDescriptorName, nil];

    [fetchRequest setSortDescriptors:sortDescriptors];

    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:@"sectionName" cacheName:@"Root"];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;

    return fetchedResultsController;
}    

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
    NSLog(@"didChangeSection");
    switch(type) {
        case NSFetchedResultsChangeUpdate: 
            [self.tableView reloadData]; 
            break;
        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;
        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}


// Notifies the delegate that section and object changes are about to be processed and notifications will be sent. 
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    [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 NSFetchedResultsChangeUpdate:
            [self configureCell:(CustomScheduleTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath]
                    atIndexPath:indexPath];
            break;
        case NSFetchedResultsChangeDelete:
            [self.tableView  deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] 
                                   withRowAnimation:UITableViewRowAnimationFade]; 
            break;
        case NSFetchedResultsChangeMove:
            [self.tableView  deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] 
                                   withRowAnimation:UITableViewRowAnimationFade]; 
            [self.tableView  insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] 
                                   withRowAnimation:UITableViewRowAnimationFade]; 
            break;      
        default:
            break;
    }   
}

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

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration {
    [self.tableView reloadData];
}

@end

更新 额外信息:

插入和删除对象发生在同一个线程中,它们使用相同的managedObjectContext并使用相同的存储库。实际上,插入第一个对象然后删除其他对象,然后有一个保存。

我使用NSManagedObjectContextDidSaveNotification保存,触发AppDelegate中的保存合并。

更新 更多代码:

AppDelegate.m

- (void)saveContext
{
    NSError *error = nil;
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil) {
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
            // Replace this implementation with code to handle the error appropriately.
            // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        } 
    }
}

-(void)mergeChanges:(NSNotification *)saveNotification {
    NSLog(@"Merging and saving");
    if ([NSThread isMainThread])
        [self.managedObjectContext mergeChangesFromContextDidSaveNotification:saveNotification];
    else
        [self performSelectorOnMainThread:@selector(mergeChanges:) withObject:saveNotification waitUntilDone:NO];
}

BacgroundThread.m

AppDelegate *theDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSNotificationCenter *notify = [NSNotificationCenter defaultCenter];

NSManagedObjectContext *newMoc = [[NSManagedObjectContext alloc] init];
[newMoc setPersistentStoreCoordinator:[theDelegate persistentStoreCoordinator]];

//缩短插入部分以进行说明(插入效果很好)

ReminderSchedule *reminderSchedule = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:newMoc];

//实际删除部分代码

if ([jsonDict objectForKey:@"deletedSchedules"] != nil) {
    NSArray *deletedSchedules = [jsonDict objectForKey:@"deletedSchedules"];

    for (int i = 0; i < [deletedSchedules count]; i++) {
        NSInteger externalScheduleID = [[deletedSchedules objectAtIndex:i] intValue];

        NSLog(@"Removing schedule %d", externalScheduleID);

        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"ReminderSchedule" inManagedObjectContext:newMoc];
        [fetchRequest setEntity:entity];

        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"externalScheduleID == %d", externalScheduleID];
        [fetchRequest setPredicate:predicate];

        NSError *error = nil;
        NSArray *result = [newMoc executeFetchRequest:fetchRequest error:&error];

        for (NSManagedObject *managedObject in result) {
            [newMoc deleteObject:managedObject];
        }
    }
}

//插入和删除后保存

NSError *saveError = nil;
if (![newMoc save:&saveError]) {
    NSLog(@"Whoops, couldn't save: %@", [saveError localizedDescription]);
} 


[[NSNotificationCenter defaultCenter] removeObserver:theDelegate];        

更新 执行插入和删除,然后保存工作。单独插入有效。单独进行删除不会有任何问题。

2 个答案:

答案 0 :(得分:0)

确保self.fetchResultsController.delegate = self;方法中包含viewDidLoad

答案 1 :(得分:0)

确保NSNotificationCenter始终使用NSManagedObjectContextDidSaveNotification注册,而不仅仅是在有要插入的内容时。