Retrieving NSManagedObjects Issue (Core Data) - ios

Here is my issue, I am using core data to store around 58 documents. All they have is 4 NSString attributes. I have a helper class that is set up to retrieve documents whenever I need them, however when I passed back the array from my initial getAllDocumentsFromCoreData, all of the attributes seem to be null when accessed in downloadDocumentPDFsAndStoreOnDeviceViaWebService.
The weird thing is when I go to view the array fetched from core data in the getAllDocumentsFromCoreData method, it shows all of the documents/attributes correctly fetched.
What am I doing wrong? I'm relatively new to Core Data, so this could be a rookie mistake.
//USE TO RETRIEVE ALL DOCUMENTS CURRENTLY STORED WITHIN COREDATA
+ (NSArray *) getAllDocumentsFromCoreData
{
CoreData_Helper *helper = [[CoreData_Helper alloc] init];
NSManagedObjectContext *context = [helper managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setReturnsObjectsAsFaults: NO];
NSEntityDescription *entity = [NSEntityDescription entityForName: #"Document" inManagedObjectContext: context];
[fetchRequest setEntity: entity];
NSError *error = nil;
NSArray *fetchedDocuments = [context executeFetchRequest: fetchRequest error: &error];
if (error)
{
NSLog(#"%#", [error localizedDescription]);
}
return fetchedDocuments;
}
+ (void) downloadDocumentPDFsAndStoreOnDeviceViaWebService
{
NSArray *fetchedDocuments = [CoreData_Helper getAllDocumentsFromCoreData];
for (Document *document in fetchedDocuments)
{
NSLog(#"%#", [document fileID]);
}
}

This is happening because:
Managed objects don't have strong references to their managed object context
When a managed object context is deallocated, any managed objects fetched from it become inaccessible, with attribute values set to nil, because they no longer have any connection to the persistent store.
In your case you're allocating a managed object context in getAllDocumentsFromCoreData and performing your fetch. You return the results but the context gets deallocated at the end of the function. By the time you look at the returned array, the context is gone and the objects are useless.
You should create the managed object context somewhere else-- probably (though not necessarily) as a property of the object where these methods exist. It's typical to have relatively long-lived context objects rather than create them locally just before performing a fetch. There are various other techniques, but the key in your case is that you must not let the context be deallocated until you're finished with everything you've fetched from it.

Related

Fetching NSManagedObject not saved

Im trying to fetch some objects that Im parsing from a JSON, I dont want to save them until some actions are being done by the user, but I want to fetch them.
Not saving them means I wont be able to fetch them in an different run of the app, but that's ok.
So Im creating the NSManagedObject like this.
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:[self managedObjectContext]];
NSManagedObject *event = [[NSManagedObject alloc] initWithEntity:entityDescription insertIntoManagedObjectContext:[self managedObjectContext]];
And then Im trying to fetch it like this.
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Event" inManagedObjectContext:[self managedObjectContext]];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
NSPredicate *testForTrue = [NSPredicate predicateWithFormat:#"id_server == %#", aIdServer];
[fetchRequest setPredicate:testForTrue];
NSArray *arrayEvents = [[NSArray alloc]initWithArray:[[self managedObjectContext] executeFetchRequest:fetchRequest error:&error]];
id_server is a unique value, and it never returns an event, that is actually there, because if I print all 'Events' in CoreData it shows the event with the id_server.
If I save the context:
NSError *error;
if (![[appDelegate managedObjectContext] save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
It finds the events, but can't I fetch events without saving the context?
Thanks
[EDIT]
The way I access the managedContext is the following:
- (NSManagedObjectContext *)managedObjectContext
{
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
Even though all attributes were set correctly, since I printed all values and they were correct, I was doing something weird, setting as a NSString something that originally(from the server)is an int.
When I was saving the context, the fetching was working correctly, but when I didnt that, somehow, became an issue.
Changing the type of that attribute, the one I was fectching, solved the issue.
The NSManagedObject class instances that you create will only remain associated with the NSManagedObjectContext that was used to create them until and unless you commit them to persistent store.
So, if you want to fetch the same objects (not saved to persistent store), you will need to have reference to same NSManagedObjectContext instance that was used to create them.
In your code, does [self managedObjectContext] creates a new managedObjectContext every time it is called or it just returns the same context ?
If it creates a new managedObjectContext everytime then you are getting a new instance of NSManagedObjectContext and hence you will not be able to fetch those NSManagedObject instances.
However, if it is returning the same instance of NSManagedObjectContext then you can use
[myFetchRequest setIncludesPendingChanges:YES];
Apple Doc
This matches against currently unsaved changes in the managed object context.

CoreData: Create temporary models and maybe save to context

I have a problem with Core Data, because I don't know the best way to handle my problem:
I load a json from a server and parse the results in ManagedObjects. At first the ManagedObjects should be temporary.
But the user can put a ManagedObject to a leaflet. And then the ManagedObject should be saved to CoreData. (The object should be accessible offline)
When the user load the same objects some time later from the server, already saved ManagedObjects should be fetched.
So I don't want to put every object in CoreData / PersistantStore the user doesn't need.
First what I do is to create a background context:
__block NSManagedObjectContext *context = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSManagedObjectContext *backgroundContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType];
backgroundContext.parentContext = context;
With a fetch I check if there is already a ManagedObject in the persistantstore.
If there is one, I will take this. else create a new ManagedObject in nil context.
NSArray *results = [backgroundContext executeFetchRequest:fetch error:&error];
if (!error && results.count == 1) {
myModel = [results objectAtIndex:0];
}
else {
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MyModel" inManagedObjectContext:backgroundContext];
myModel = (MyModel *)[[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
}
And I do the same with every relationship:
if (! myModel.relation) {
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Relation" inManagedObjectContext:backgroundContext];
myModel.relation = (Relation *)[[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:myModel.managedObjectContext];
}
Works fine so far with creating the models.
But how to save one model?
The managedObjectContext is nil. If I call save: on the managedObjectContext, it saves everything.
In my AppDelegate i wrote a function to insert a ManagedObject in the main ManagedObjectContext:
- (void)insertObjectAndSave:(NSManagedObject *)managedObject {
if (!managedObject.managedObjectContext) {
[self.managedObjectContext insertObject:managedObject];
}
[self saveContext];
}
Is this a good solution? or is there a better way to save temporary ManagedObjects in the main ManagedObjectContext?
Excellent Answered My Mundi..
Here is on more scenario to create NSManagedObject temporary, whether we can make it permanents If we want.
Create temporary object
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:myMOC];
NSManagedObject *unassociatedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
Code this if you wants to save it permanently
[managedObjectContext insertObject:unassociatedObject];
NSError *error = nil;
if (![managedObjectContext save:&error])
{
//Respond to the error
}
You could not create the objects with nil context but with a "temp" context. If you want to save them, call [tempContext save:nil];. If you want to discard them, just throw away the context.
Another strategy is to avoid the complexity of multiple context altogether by adding a simple boolean attribute temp to your entity (if relationships are one-to-many, only the top level entity needs to have this). You can save by default and display only non-temp items. You can delete temp items, including all related items, immediately or periodically.

Does Core Data persist the order of objects in NSMutableArray?

I basically operate on objects in a NSMutableArray, which is fetched from Core Data. All changes to those objects save in Core Data with the exception of the order of objects in the array. When, for instance, an object in the array is moved to the end of it, that does not seem to save in Core Data. Is it the case that Core Data does not persist order of objects in arrays? If so, what can be done to work around it? Sorting?
This is how I fetch data:
NSError *error;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"Item" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
self.items = [[managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
And this is how I save changes:
- (void)saveCoreData{
NSManagedObjectContext *context = [self managedObjectContext];
NSError *error;
if (![context save:&error]) {
NSLog(#"Could not save data: %#", [error localizedDescription]);
}
}
If you're saving the array in a transform able property on an entity then the order will be saved - if you set the array back onto the managed object instance.
If the array is a fetched list of managed object instances then the order of that array means nothing and won't be saved. If you want to save this order then you need to add (and update) some other data in the context. This could be an order attribute on the entity or another entity with an ordered relationship perhaps.
If you're not using ordered relationships, then there is no guarantee of the order.
You can either set your relationships to be ordered. In this case you will have to deal with NSOrderedSet and different accessory methods. This feature is available in iOS5 and later.
Here is a great article of Ash Furrow (great developer, had a privilege to meet him) that covers ordered relationships in Core Data.
On the other hand, you can order your data once you access it. In my case I had an NSArray property that, once accessed, would get all objects in NSSet and order them. The disadvantage of this approach is every time you add new NSManagedObject to a relationship, mentioned NSArray will become outdated and must be recreated.

Reading Core Data as Objects

I have 1 entity in my database "Message" with the values MessageID, messageText and i want to read every row of Core Data, make an object of my class "Message" and put the new object into an array.
It's the first time I'm using Core Data and I don't quite get it yet, how I manage to do that.
Create a fetch request for the entity you wish to retrieve. Don't give it a predicate, set whatever sort descriptor you want.
Execute the fetch request in a managed object context and it will return an array of all the objects of that entity.
This is purposely just a descriptive answer, you can find the specifics of how to do this from the Core Data introductory documentation; you are new in Core Data and this is a good way to learn it.
Also - don't think of Core Data in terms of rows of data that you turn into objects. It's an Object-Relationship graph. It stores the objects of entities and their relationships between them. You don't turn the "rows" into objects, you get the objects back directly.
The response of #Abizern with code :
NSManagedObjectContext *moc = // your managed object context;
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"Message" inManagedObjectContext:moc];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDescription];
// You can also add a predicate or sort descriptor to your request
NSError *error;
NSArray *array = [moc executeFetchRequest:request error:&error];
if (array == nil)
{
// Deal with error...
}

CoreData updating an NSManagedObject more than once saves several copies?

I have 90 CoreData entities called "ItemModel" with 2 attributes 'uid', 'description', where each of the item is inserted as an NSManagedObject:
NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName: #"ItemModel" inManagedObjectContext: AFYDelegate.managedObjectContext];
The first server call assigns the 'uid' to each of the 90 items fetched above for key "uid". The context is not saved here.
On a later second server call, I like to update 'description' for the 90 items, for each of the NSManagedObject using indexPath - by fetching and passing each object to the following method and saving the context:
[self updateItemToDataModel:object withData: description];
....
....
- (void)updateItemToDataModel:(NSManagedObject *) object withData:(NSString *)data
{
[object setValue:data forKey:#"description"];
NSError * error = nil;
if (![self.managedObjectContext save:&error]) {
//Handle any error with the saving of the context
NSLog(#"%#",error.localizedDescription);
}
}
The above works fine in updating CoreData BUT after closing the Simulator and running the code again, there will be two duplicates for each item with the same 'uid' and 'description'. This means I have 180 items now. Repeatedly closing and running the code creates more and more items.
I tried removing updateItemToDataModel method, resetting the Simulator and it works fine with 90 items.
I'm new to CoreData if someone can help. What's wrong with my code if I only wished to update existing items?
You are inserting a new object into the MOC (managed object context) each time--instead of doing a fetch and finding an existing instance of the object you wish to update.
To fetch the existing object you might execute a fetch request like so...
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"uid == %#", uidToMatch];
NSFetchRequest * fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setPredicate:predicate];
[fetchRequest setEntity:[NSEntityDescription entityForName:#"ItemModel" inManagedObjectContext:managedObjectContext]];
NSError * error = nil;
NSArray * results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if ([results count]) {
// you may need to handle more than one match in your code...
// you could also set a fetch limit of 1 and guarantee you only get the first object, eg: [fetchRequest setFetchLimit:1];
}
else {
// no results
}
You might want to wrap that in a helper function so you can re-use it. And read up on NSFetchRequest, NSPredicate and writing predicates in order to do fancier fetch requests.

Resources