Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Core Data not saving objects persistently

I'm new to Core Data and as such am not sure if I'm making a mistake. I've downloaded some data from a REST API and it successfully saves the JSON response to disk. I'm trying to process the data and save it persistently using Core Data.

NSLog(@"inserted objects: %@", [managedObjectContext insertedObjects]);
    [managedObjectContext performBlockAndWait:^{
        NSError *error = nil;
        if (![managedObjectContext save:&error]) {
            NSLog(@"Unable to save context for class %@", className);
        } else {
            NSLog(@"saved all records!");
        }
    }];

I've successfully processed the JSON and added it to an NSManagedObjectContext. In the first line, it shows that I've successfully attempted to insert 2 objects.

inserted objects: {(
    <User: 0xa259af0> (entity: User; id: 0xa259b70 <x-coredata:///User/t44BB97D0-C4B4-4BA6-BD25-13CEFDAE665F3> ; data: {
    email = "[email protected]";
    experience = "2013-07-20";
    "first_name" = Vishnu;
    id = 2;
    "job_title" = Developer;
    "last_name" = Prem;
    location = "";
    "phone_number" = "+6590091516";
    "profile_pic" = "";
    "thumbnail_profile_pic" = "";
    "user_id" = 2;
}),
    <User: 0xa25e460> (entity: User; id: 0xa25e4c0 <x-coredata:///User/t44BB97D0-C4B4-4BA6-BD25-13CEFDAE665F2> ; data: {
    email = "[email protected]";
    experience = "2013-07-20";
    "first_name" = Sanchit;
    id = 1;
    "job_title" = Developer;
    "last_name" = Bareja;
    location = "";
    "phone_number" = "+15106127328";
    "profile_pic" = "";
    "thumbnail_profile_pic" = "";
    "user_id" = 1;
})
)}

When I attempted [managedObjectContext save:&error], it does so successfully and print out "saved all records" as expected. However, when I go to my application .sqlite file and check for added objects, I realize that it hasn't added any objects to the db.

On app relaunch, I print out a list of objects that are already in the database and it confirms that I've none saved yet.

Does anyone know what's going on and why I'm not able to save the data persistently even though it looks like I've successfully created the 'User' objects that needs to be saved in the Core Data model.

EDIT:

here is where I create the NSPersistentStoreCoordinator

// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }

    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"RTModel.sqlite"];

    NSError *error = nil;
    NSLog(@"Test 1");
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    NSLog(@"Test 2");

    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&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.

         Typical reasons for an error here include:
         * The persistent store is not accessible;
         * The schema for the persistent store is incompatible with current managed object model.
         Check the error message to determine what the actual problem was.


         If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.

         If you encounter schema incompatibility errors during development, you can reduce their frequency by:
         * Simply deleting the existing store:
         [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]

         * Performing automatic lightweight migration by passing the following dictionary as the options parameter:
         [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

         Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.

         */
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    return _persistentStoreCoordinator;
}

I have 3 contexts.

  • masterManagedObjectContext

  • backgroundManagedObjectContext

  • newManagedObjectContext

master is parent of both background and new. When I query the contexts like this:

    NSError *error = nil;
    NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"User"];
    [request setSortDescriptors:[NSArray arrayWithObject:
                                 [NSSortDescriptor sortDescriptorWithKey:@"id" ascending:YES]]];
    [request setReturnsObjectsAsFaults:NO];
    NSArray *testArray = [[[RTCoreDataController sharedInstance] newManagedObjectContext] executeFetchRequest:request error:&error];

    for (User *obj in testArray) {
        NSLog(@"obj.id %@", obj.id);
    }

    NSLog(@"query records: %@",testArray);

master and background both return the correct obj.id in the NSLog as well as gives the output below for @"query records"

   (
    "<User: 0xa3811d0> (entity: User; id: 0xa381230 <x-coredata:///User/t92BCED2D-CD17-49CC-9EBA-DF8F52F06A002> ; data: {\n    email = \"[email protected]\";\n    experience = \"2013-07-20\";\n    \"first_name\" = Sanchit;\n    id = 1;\n    \"job_title\" = Developer;\n    \"last_name\" = Bareja;\n    location = \"\";\n    \"phone_number\" = \"+15106127328\";\n    \"profile_pic\" = \"\";\n    \"thumbnail_profile_pic\" = \"\";\n    \"user_id\" = 1;\n})",
    "<User: 0xa382170> (entity: User; id: 0xa3820b0 <x-coredata:///User/t92BCED2D-CD17-49CC-9EBA-DF8F52F06A003> ; data: {\n    email = \"[email protected]\";\n    experience = \"2013-07-20\";\n    \"first_name\" = Vishnu;\n    id = 2;\n    \"job_title\" = Developer;\n    \"last_name\" = Prem;\n    location = \"\";\n    \"phone_number\" = \"+6590091516\";\n    \"profile_pic\" = \"\";\n    \"thumbnail_profile_pic\" = \"\";\n    \"user_id\" = 2;\n})"
)

however "new" returns (null) for the obj.id in NSLog and returns the following for @"query records":

(
    "<User: 0xa2b08a0> (entity: User; id: 0x95aebe0 <x-coredata:///User/tBFCC6C5F-7D2C-4AA0-BA96-B806EE360A762> ; data: <fault>)",
    "<User: 0xa2b0910> (entity: User; id: 0xa4b9780 <x-coredata:///User/tBFCC6C5F-7D2C-4AA0-BA96-B806EE360A763> ; data: <fault>)"
)
like image 980
Sanchit Bareja Avatar asked Aug 17 '13 02:08

Sanchit Bareja


People also ask

What is persistence in Core Data?

Object graph management: Core Data works with defined objects, and core data keeps track of these objects and their relationships with each other. Persistence: Data can be retrieved, in this case, either from the device or from a network location. SQLite: One of the potential data stores that Core Data can use.

How do I save an object in Core Data?

To save an object with Core Data, you can simply create a new instance of the NSManagedObject subclass and save the managed context. In the code above, we've created a new Person instance and saved it locally using Core Data.


2 Answers

From your code and the comments it seems that you are not saving the master context. Make sure you call

[managedObjectContext save:&error]; 

on all child contexts that save the data, and after that on the master context as well.

like image 57
Mundi Avatar answered Sep 25 '22 06:09

Mundi


I just got done banging my head against essentially the same problem. A UITableViewController fetched a subclass of NSManagedObject from the NSManagedObjectContext, checked if an attribute was nil, and if it was downloaded the data, set that attribute, then saved the NSManagedObjectContext. Something like this:

MyManagedObject *mgObject = //get object from NSFetchResultsController
NSManagedObjectContext *mgObContext = mgObject.managedObjectContext;
if (!mgObject.data)
{
    mgObject.data = [NSData dataWithContentsOfURL:urlWithData];
    [mgObContext performBlock ^{
        NSError *saveError = nil;
        BOOL saveResult = [mgObContext save:&saveError];
        if (saveError || !saveResult)
        {
            NSLog(@"Save not successful..");
        }
    }];
}
//do something with myObject.data

The save function was giving a YES boolean return and saveError was remaining nil, but if I quit the app and relaunched, when my Core Data loaded up my NSManagedObject subclasses, the data attribute was nil, and when this UITableViewController came back up, it had to download the data again.

I couldn't really find a solution to this anywhere… reading through the Core Data documentation didn't help. The solution came to me when I considered the difference between the above code and my code that sets the attributes in the NSManagedObject subclass's factory methods, which is basically:

MyManagedObject *mgObject = [NSEntityForDescription insertNewObjectForEntityName:@"MyManagedObject" inManagedContext:mgObContext];
mgObject.attribute1 = some value
mgObject.attribute2 = another value

The only difference is that I'm calling the factory methods from inside a [mgObContext performBlock:].

So the amended code is:

MyManagedObject *mgObject = //get object from NSFetchResultsController
NSManagedObjectContext *mgObContext = mgObject.managedObjectContext;
if (!mgObject.data)
{
    [mgObContext performBlock: ^{
        mgObject.data = [NSData dataWithContentsOfURL:urlWithData];
        NSError *saveError = nil;
        BOOL saveResult = [mgObContext save:&saveError];
        if (saveError || !saveResult)
        {
            NSLog(@"Save not successful..");
        }
    }];
}
//do something with myObject.data

Which, thus far, is working perfectly. So I think anytime you made modifications to NSManagedObjects' attributes, you need to do so on the their NSManagedObjectContext's thread.

like image 41
paulmrest Avatar answered Sep 24 '22 06:09

paulmrest