I am using RestKit in my iOS app and up until now I was simply using object mapping because I wasn't persisting any data to CoreData. I now want to add the possibility for the users to download some data, and use that data if the user has no internet connection.
I understand I have to use Entity Mapping for that, but I'm running into an issue: how can I use two different mappings for the same request? I mean, I don't understand how I am supposed to handle these two cases. Whether the users decided to download the data or is just requesting it to display once, the URL path will be exactly the same. How can I tell RestKit once to store it in CoreData and the other time to simply map it with ObjectMapping?
Basically, I'm asking the same question as this one: How to use Core Data models without saving them?
but specifically for RestKit instead of MagicalRecords.
I just had a similar issue: I needed to obtain a token that was being returned in addition to a user object that I am mapping to Core Data. The token is sent on it's own in the JSON response so I wasn't sure how to extract it.
In the end I used the following:
[operation setWillMapDeserializedResponseBlock:^id(id deserializedResponseBody) {
NSDictionary *dictionary = [[NSMutableDictionary alloc] init];
dictionary = deserializedResponseBody;
self.token = [dictionary objectForKey:#"token"];
return deserializedResponseBody;
}];
The JSON is in the format:
{
“token”: “....”,
“user”: {
....
}
}
The operation setWillMapDeserializedResponseBlock method gives you the chance to manipulate the results before mapping takes place - or to grab other data that's not covered by your object mapping. Works nicely.
The right way to handle this case is to use different ManagedObjectContexts.
You will need one for the persistent data, and it can be set up like this:
// Initialize managed object store
RKManagedObjectStore *managedObjectStore = [[RKManagedObjectStore alloc] initWithManagedObjectModel:managedObjectModel];
objectManager.managedObjectStore = managedObjectStore;
[managedObjectStore createPersistentStoreCoordinator];
[[RKManagedObjectStore defaultStore].mainQueueManagedObjectContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
Then, you can create a second context, that will be temporary only:
NSManagedObjectContext *newTemporaryContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; // Choose your concurrency type, or leave it off entirely
[newTemporaryContext setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
newTemporaryContext.persistentStoreCoordinator = coordinator;
Finally, once this is done, you should store a reference to your temporary context somewhere, and decide on which context to used, based on the context of your app.
Related
I'm new to Core Data and trying to use it for persisting objects for offline support in an app communicating with JSON Back-end.
I was using NSObjects for my models and now use NSManagedObjects.
I only need to save these server objects in a few parts of the app, and in other parts, keep using the previous behavior, without persistence:
Fetch from server -> Parse JSON response -> Create Objects without persistence to Core Data -> Display in UI
For that purpose I was using initializers like this one
- (id)initObjectWithJSON:(NSDictionary *)JSONDictionary
{
self = [super init];
if (!self) {
return nil;
}
self.property1 = JSONDictionary[#"property1"];
self.property2 = JSONDictionary[#"property2"]
...
}
Do I now have to use the initializer initWithEntity:insertIntoManagedObjectContext: and thus create a new context even if I don't want to persist objects to core data?
Is there another approach to "separate" the objects which need persistence and the objects that don't need persistence to keep using the old methods like the one above?
Initialize it without a context:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"MyEntity" inManagedObjectContext:myMOC];
NSManagedObject *unassociatedObject = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
Source: How to Deal with Temporary NSManagedObject instances?
I'm trying to best format my project's use of RestKit and Core Data. There are a couple of things that I've got working, but I have a feeling they are implemented poorly and potentially thread unsafe... I have an object that manages all of my data transfer, storage, etc., which has a function that sets up restkit. I have an instance variable that I use for RKObjectManager, and in this setup function I create the objectStore, setup all the attribute mappings, create the persistent store, etc., - all of the normal restkit setup code. Outside of this function, the only thing available to this object is the _objectManager instance variable, which I've been using for NSFetchRequests and such.
There are two things I want to make sure I'm implementing properly, fetching managed objects, and savings changes to managed objects.
If I want to update a property on an object, I've been doing this:
object.whatever = #"something here";
NSError *error;
if (![object.managedObjectContext save:&error]) {
// log the error here
}
Is this the proper way to update/save a property on an object? Is accessing the object's managed object context directly save to do at any point in the code, or is this something that should be done only in the background/foreground? My current implementation could potentially have this called in both the background and foreground and I just want to make sure this is acceptable.
When I want to fetch an object, I wrote a function that takes an entity name, an array of predicates, and a sort descriptor as parameters so it can be reused:
NSManagedObjectContext *managedObjectContext = // I DONT KNOW WHAT TO PUT HERE! //
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:entityName inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSPredicate *compoundPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:predicates];
[fetchRequest setPredicate:compoundPredicate];
NSError *error;
NSArray *fetchedRecords = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (error) {
// log error
}
// if we were given a sort descriptor, sort the array appropriately
if (sortDescriptor) {
fetchedRecords = [fetchedRecords sortedArrayUsingDescriptors:#[sortDescriptor]];
}
return fetchedRecords;
My problem here is creating/accessing the correct managed object context. How should I do this? Do I access some property on the RKObjectManager I created before such as:
_objectManager.managedObjectStore.mainQueueManagedObjectContext
or is that not thread safe because its for the main thread? What can I do to make sure I'm using the right managed object context, and that it is thread safe? I was using:
_objectManager.managedObjectStore.persistentStoreManagedObjectContext
but I was told this was definitely not best practice and was not thread safe, so I'm trying to determine the best solution.
EDIT - perhaps I can call this function to get the context whenever I want to fetch objects?
- (NSManagedObjectContext *)getManagedObjectContext {
if ([NSThread isMainThread]) {
return _objectManager.managedObjectStore.mainQueueManagedObjectContext;
}
else {
return [_objectManager.managedObjectStore newChildManagedObjectContextWithConcurrencyType:NSPrivateQueueConcurrencyType tracksChanges:YES];
}
}
For saving, instead of this:
if (![object.managedObjectContext save:&error]) {
you should do:
if (![object.managedObjectContext saveToPersistentStore:&error]) {
so that the changes are persisted right up the chain to the on-disk store. You should only be doing this on the thread that created / fetched the managed object (thus the thread ownership of the MOC is maintained).
Foreground / background isn't important so much as which MOC is used by each thread. If the MOC thread ownership is respected then you should be fine.
Same applies to fetching. For UI updates, you must use the main thread and the mainQueueManagedObjectContext. You should never directly use the persistentStoreManagedObjectContext. For arbitrary background threads you should be asking the managed object store to create a new child managed object context for you and using that.
I am new to the IOS programming, currently learning core data, I went into running the code where i need to save only specific objects in core data. So how can i do that?
According to the scenario, i have data from server as well as local storage (core data), but when user close the app (went to background) I want to store the data in the server(if net available) if not then in the local storage (but selected only - means specific data should be stored, there are objects which came from online server which i dont want to store on local).
Please let me know any solution if possible.
Regards
Nisar Ahmed
I see two ways to achieve this:
Iterate through inserted and updated objects and revert those you do not wont to save. Inserted objects should be deleted, updated should be refreshed:
for (NSManagedObject* obj in [self.managedObjectContext insertedObjects]) {
if (/*Shouldn't be saved*/) {
[self.managedObjectContext deleteObject:obj];
}
}
for (NSManagedObject* obj in [self.managedObjectContext updatedObjects]) {
if (/*Shouldn't be saved*/) {
[self.managedObjectContext refreshObject:obj mergeChanges:NO];
}
}
Create separate managed object context. Recreate objects that you want to save in new context and then save it.
NSManagedObjectContext* newContext = [[NSManagedObjectContext alloc] init];
[newContext setPersistentStoreCoordinator:[self.managedObjectContext persistentStoreCoordinator]];
for (NSManagedObject* obj in objectsWantToSave) {
NSEntityDescription* entity = [obj entity];
NSDictionary* valuesByKeys = [obj dictionaryWithValuesForKeys:[[entity attributesByName] allKeys]];
NSManagedObject* objCopy = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:newContext];
[objCopy setValuesForKeysWithDictionary:valuesByKeys];
}
[newContext save:NULL];
The second approach is better for my opinion.
Have a look into UIManagedDocument - http://developer.apple.com/library/ios/#documentation/uikit/reference/UIManagedDocument_Class/Reference/Reference.html
It takes care of a lot of the boilerplate involved in using core data.
Three Questions but they are all related. If you like I can divide them into three questions so that you can more credits. Let me know if you'd like for me to do that.
I have the following code that allows me to access NSManagedObject
self.managedObjectContext = [(STAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSError *error;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"LetsMeet" inManagedObjectContext:managedObjectContext]];
NSArray *objectArray = [managedObjectContext executeFetchRequest:request error:&error];
if(objectArray.count==0){
letsMeet = (LetsMeet *) [NSEntityDescription insertNewObjectForEntityForName:#"LetsMeet" inManagedObjectContext:managedObjectContext];
} else{
letsMeet = (LetsMeet *)[objectArray objectAtIndex:0];
}
The code above allows me to save and retrieve attributes. i.e. I can access letsMeet.attribute to save and fetch.
Question 1: How do I delete and start a brand new managedObjectContext. i.e. User has a form that he's been filling out between the scenes. Everything is saved to CoreData from each scene as the user hits the Next button on the navigation Controller. After going through several screens, the user wants to cancel the form. At this point I would like to delete everything that has been saved thus far. Code example please.
Question 2: Lets say the user gets towards to end of the form and decides to save the form for later retrieval. How do I save a copy of the entire form as one object in Core Data. Code example please.
Question 3: How do I retrieve that saved object from Core Data at a later time and display what all the user had saved? Code example please.
To delete you just need to delete letsMeet object from NSManagedObjectContext.
NSError *error;
[managedObjectContext deleteObject:letsMeet];
[managedObjectContext save:&error];
Since you always have only one object, getting the reference of letsMeet is not a problem. You can do as you did in your code.
Update:
And you don't need to delete the managed object context. It just a space to deal with your objects. More explanation at the end of question.
2. If the LetsMeet entity is modeled in a way that all the form elements are attributes of LetsMeet, when you save the managedObjectContext after creating a LetsMeet object as you have done in code, this will be saved as a single object.
3.You already know how to retrieve an object as thats what you are doing in the code. Everything becomes easy as you are only using one object.
In the case of multiple objects to get a the unique object, you should either implement a primary key,(maybe formID, i.e; add another attribute to LetsMeet) or you should know what the objectId of each object is and then set the predicate of your fetch request accordingly.
NSFetchRequest *request = [[NSFetchRequest alloc]init];
[request setEntity:letsMeet];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"formId like %#", formId];
[request setPredicate:predicate];
NSArray *resultsArray =[managedObjectContext executeFetchRequest:request error:&error];
If your formId is unique, this will return you a single object array.
But if you are using core-data for only handling one object, you could've used NSUserDefaults or write to a plist File to do this. This is kind of overkill.
Update:
To get the objectId of a NSManagedObject:
[letsMeet objectId];
ManagedObjectContext is like a whiteboard. The object you have inside the array, the object inside managed object context, its all the same. You can change the objects, add object, delete object etc. Only thing is whatever is the current state of the object(s) when you do a [managedObjectContext save:] , that is written to disk.
I'm using 2 managed object contexts for efficiently important a large data set in the background. I'm ensuring I'm only using 1 managed object context at a time in the thread.
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:[self persistentStoreCoordinator]];
MetricType *metricType = [self metricTypeForName:self.metricName fromContext:context];
NSLog(#"metric context %#", [metricType managedObjectContext]);
[self processDataInContext:context forMetric:metricType];
In the snipped of code above, the NSLog correctly prints out the address of the managedObjectContext i'm using. I then go on to processDataInContext - which is just a private method to interate over a json data array and add objects. Each object has a relationship to the MetricType.
However, when I go to associate them
metric.metricType = metricType;
I get the error: Illegal attempt to establish a relationship 'metricType' between objects in different contexts.... even though I'm ensuring I don't do this.
When I do a log output before this line:
NSLog(#"My Context %#", context);
NSLog(#"metric context %#", [metricType managedObjectContext]);
The metricType context returns nil!!
How has it become nilled? I didn't nil it and this seems to be the reason its complaining.
I figured it out
[context reset];
..was being called every 50 records, which of course was removing my metricType object from the context and I wasn't re-fetching it.
The way you initialize metricType looks fishy. What does [self metricTypeForName:fromContext:] actually do to return a MetricType instance? Are you creating a new MetricType using [[MetricType alloc]init]? I suspect you are returning something in an auto-release pool