Search code examples
iosobjective-ccore-dataentitynsfetchrequest

Reset all saved data in Core Data model


I have a method which allows the user to export a .csv file of all data in my Core Data model. I'm using the wonderful CHCSV Parser after performing a fetchRequest to fetch the stored results. Once the .csv has been created, it gets attached to an email and exported from the device. This all works fine, the issues come when I delete the data.

I am using a somewhat popular technique to delete my data (by popular I mean it has been recommended on lots of Stack Overflow answers I have came researched). I simply fetch all objects and delete them one by one.

// Create fetchRequest
NGLSAppDelegate *appDelegate = (NGLSAppDelegate *)[[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"NGLS"
                                                      inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];

// Delete objects
for (Model *results in fetchedObjects) {
    [context deleteObject:(id)results];
    NSLog(@"NGLS objects deleted");
};

My model is simple and only has two entities, so I use the same code for the other Admin entity too. This is all fine, all objects are deleted, and I can confirm this by doing another fetchRequest which returns the object count for each entity - both have a count of zero. The problem occurs when I try to save data back to either entity after the delete has been executed.

The ViewController with the above code is an "Admin" control screen, where the user can login and also export the data. So when the user logs in, here is the code to save:

    // Save login details
    [_managedObjectAdmin setValue:user forKey:@"userLogin"];
    [_managedObjectAdmin setValue:site forKey:@"siteLocation"];

    NSError *error;
    [[self.managedObjectAdmin managedObjectContext] save:&error];

I then perform the fetchRequest above and export all data when the "Export" button is pressed. After that is complete, when I try to login on the same ViewController, the output is:

2014-10-27 12:43:49.653 NGLS[19471:607] <NSManagedObject: 0x7a8c2460> (entity: Admin; id: 0x7a82e3e0 <x-coredata://3C9F3807-E314-439C-8B73-3D4459F85156/Admin/p30> ; data: <fault>)

If I navigate back to another ViewController (using my navigation controller), then go back to the "Admin" control screen and try to login again, I get this output:

2014-10-27 12:44:04.530 NGLS[19471:607] *** Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0x7a82e3e0 <x-coredata://3C9F3807-E314-439C-8B73-3D4459F85156/Admin/p30>''

(I can post the full log if requested).

I have tried many things in an attempt to resolve this issue. Core Data is complex and I dont quite understand what I need to do to fix this. I have tried to delete the persistentStore and create it again, and the same with the managedObjectContext but nothing I have tried has worked. I implemented a resetStore method in my AppDelegate to delete and rebuild the store as follows:

- (void)resetStores {
    NSError *error;
    NSURL *storeURL = [[_managedObjectContext persistentStoreCoordinator] URLForPersistentStore:[[[_managedObjectContext persistentStoreCoordinator] persistentStores] lastObject]];
    // lock the current context
    [_managedObjectContext lock];
    [_managedObjectContext reset];//to drop pending changes
    //delete the store from the current managedObjectContext
    if ([[_managedObjectContext persistentStoreCoordinator] removePersistentStore:[[[_managedObjectContext persistentStoreCoordinator] persistentStores] lastObject] error:&error])
    {
        // remove the file containing the data
        [[NSFileManager defaultManager] removeItemAtURL:storeURL error:&error];
        //recreate the store like in the  appDelegate method
        [[_managedObjectContext persistentStoreCoordinator] addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error];//recreates the persistent store
    }
    [_managedObjectContext unlock];
}

And also tried this method:

- (NSPersistentStoreCoordinator *)resetPersistentStore
{
    NSError *error = nil;
    if ([_persistentStoreCoordinator persistentStores] == nil)
        return [self persistentStoreCoordinator];

    _managedObjectContext = nil;

    NSPersistentStore *store = [[_persistentStoreCoordinator persistentStores] lastObject];

    if (![_persistentStoreCoordinator removePersistentStore:store error:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    // Delete file
    if ([[NSFileManager defaultManager] fileExistsAtPath:store.URL.path]) {
        if (![[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:&error]) {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }

    // Delete the reference to non-existing store
    _persistentStoreCoordinator = nil;
    NSPersistentStoreCoordinator *r = [self persistentStoreCoordinator];

    return r;
}

But neither of these work. What I have found to work is after the export has completed, by closing and reopening the app everything works as normal again. I have tried to figure out what process occurs when closing/opening the app in regards to Core Data but I just don't know what to look for. I have researched many questions on Stack Overflow and tried all the solutions but nothing works for me. I really need some help on this, or else I'm going to have to force the user to quit the app after the export is done by a exit(0); command (because it's not getting submitted to the App Store, its for in-house employees) although I don't want that solution, surely there is a way I can just reset my database and continue to use the app without having to shut it down every time. Thanks in advance.

References:
Deleting all records from Core Data
Reset a Core Data persistent store
How do I delete all objects from my persistent store in Core Data


Solution

  • I have managed to solve the issue by simply refreshing the view. By calling my viewDidLoad method after the export is complete I create a new managedObject ready to use as normal, without leaving that ViewController or exiting the app altogether.

    - (void)viewDidLoad
    {
        [super viewDidLoad];
        // Do any additional setup after loading the view from its nib
    
        // Create fetchRequest
        NGLSAppDelegate *appDelegate = (NGLSAppDelegate *)[[UIApplication sharedApplication]delegate];
        NSManagedObjectContext *context = [appDelegate managedObjectContext];
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"Admin" inManagedObjectContext:context];
        [fetchRequest setEntity:entity];
    
        NSError *error = nil;
        NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
    
        // Fetch last object
        Model *admin = [fetchedObjects lastObject];
    
        // Populate login fields with last stored details
        if (admin.userLogin == nil) {
            //NSLog(@"Login empty");
        } else {
            self.usernameField.text = admin.userLogin;
            self.siteLocationField.text = admin.siteLocation;
        }
    
        // Create new managed object using the Admin entity description
        NSManagedObject *ManagedObjectAdmin;
        ManagedObjectAdmin = [NSEntityDescription insertNewObjectForEntityForName:@"Admin"
                                                           inManagedObjectContext:context];
        // Declare managed object
        self.managedObjectAdmin = ManagedObjectAdmin;
    
        // Save context
        NSError *saveError = nil;
        [context save:&saveError];
    
    // ... more stuff
    }
    

    The solution was so simple all along. I will leave this question/answer up so other people can benefit from it if they find themselves in my situation. Thanks.

    EDIT: Re-creating the managedObjectAdmin anywhere after the export works too, there is no specific need to call the viewDidLoad method. After the export has completed, I can simply create the new managedObject and save the context and continue using the app as normal, without having to navigate to another ViewController first or restarting the app.