RestKit 0.20.3 + Core Data - Skip mapping operation - ios

I'm working on an app and I need to sync objects with my API. I use RestKit 0.20.3.
The sync process begins by pulling objects from the server, and then pushes the objects that have been modified inside the app by the user.
Therefore, during the pull step, I need to ignore the objects that have been modified locally, so they won't be overriden with the server version.
I use CoreData for my objects, and I set a boolean property "modified" to YES for those that are locally modified.
So after a GET during the sync, I need to skip the mapping step for the objects having this "modified" property, but I can't find exactly how I'm supposed to do that.
The only way I've found so far is by adding a condition directly inside the RKMappingOperation, but it's dirty.
Is there a better way to do that in RestKit (and by not modifying the RestKit code)?

Well, I kept on investigating, and I just found the solution.
It's actually possible to register a custom class as RKResponseMapperOperation data source :
[RKManagedObjectResponseMapperOperation registerMappingOperationDataSourceClass:[MyCustomDataSource class]];
MyCustomDataSource should implement the RKMappingOperationDataSource protocol.
Mine inherits from RKManagedObjectMappingOperationDataSource since I'm using managed objects through Core Data.
#interface SUManagedObjectMappingOperationDataSource : RKManagedObjectMappingOperationDataSource
#end
In the implementation, I just overridden the following method to add my skipping logic to the existing one :
- (BOOL)mappingOperationShouldSkipPropertyMapping:(RKMappingOperation *)mappingOperation

Related

Why does storing a reference to an NSManagedObject prevent it from updating?

This question is poorly phased but this can be better explained in code.
We have a Core Data Stack with private and main contexts as defined by Marcus Zarra here: http://martiancraft.com/blog/2015/03/core-data-stack/
We call a separate class to do a fetch request (main context) and return an array of NSManagedObjects:
NSArray *ourManagedObjects = [[Client sharedClient].coreDataManager fetchArrayForClass:[OurObject class] sortKey:#"name" ascending:YES];
We then do some processing and store a reference:
self.ourObjects = processedManagedObjects
Our view contains a UITableView and this data is used to populate it and that works just fine.
We change the data on our CMS, pull to refresh on the UITableView to trigger a sync (private context) and then call this same function to retrieve the updated data. However, the fetch request returns the exact same data as before even though when I check the sqlite db directly it contains the new data. To get the new values to display I have to reload the app.
I have discovered that if I don't assign the processedManagedObjects to self, the fetch request does indeed return the correct data, so it looks like holding a reference to the NSManagedObject stops it from getting new data from the main context. However I have no idea why that would be.
To clarify, we're pretty sure there's nothing wrong with our Core Data Stack, even when these managed objects are not being updated, other are being updated just fine, it's only this one where we store a local reference.
It sounds like what's going on is:
Managed objects don't automatically update themselves to reflect the latest data in the persistent store when changes are made via a different managed object context.
As a result, if you keep a reference to the objects, they keep whatever data they already had.
On the other hand if you don't keep a reference but instead re-fetch them, you get the new data because there was no managed object hanging around with its old data.
You have a few options:
You could keep the reference and have your context refresh the managed objects, using either the refresh(_, mergeChanges:) method or refreshAllObjects().
If it makes sense for your app, use an NSFetchedResultsController and use its delegate methods to be notified of changes.
Don't keep the reference.
The first is probably best-- refreshAllObjects() is probably what you want. Other options might be better based on other details of your app.
Try setting the shouldRefreshRefetchedObjects property of the fetch request to true. According to the documentation:
By default when you fetch objects, they maintain their current property values, even if the values in the persistent store have changed. Invoking this method with the parameter true means that when the fetch is executed, the property values of fetched objects are updated with the current values in the persistent store.

RestKit serialization creating duplicate entries

When saving to my database with Restkit, I get duplicate entries.
I'm not sure how to prevent this. The intended behavior is that if the object already exists, then it should update that existing object with the columns that happen to be different.
I set a key identifier here:
[mapping setIdentificationAttributes:#[MYObjectAttributes.userID]];
but I suppose there is something else I am supposed to do. I've seen other questions more related to core-data that manually do a fetch request looking for an existing entry, before writing it, this seems expensive and restkit is supposed to have a solution for this already.
RestKit is for mapping a RESTful service to core data. If you are not using the RKObjectManager for updating (that is, you want to put something on your REST service) and only want to do a local change you should get the managed object and work with it outside the context of RestKit.
If you need to check whether a managed object exists locally or not, you should do it with a Managed Object Context rather than try to use RestKit for it.
Along with attributes, you can also detect, whether managed object is new or not. RestKit has created a category over NSManagedObject, where it has provided 1 function:
/**
* Returns YES when an object has not been saved to the managed object context yet
*/
#property (nonatomic, readonly) BOOL isNew;
https://github.com/RestKit/RestKit/blob/fc101de9133d96bc0e2221153de7f699f8c1f06d/Code/CoreData/NSManagedObject%2BRKAdditions.m

RestKit: Managed object mapped inside a plain old Objective-C object

Preamble: I'm using RestKit 0.21.0, but I've also tried 0.23.0.
I have a number of mappings where a plain old Objective-C object (let's call that a POOCO) contains an NSManagedObject. It seems that when doing GET requests on this root POOCO, the managed object is created under the mainQueueManagedObjectContext.
However when I do a PUT against the POOCO, when the response is loaded (the response is a copy of the object that was sent), the managed sub-object is created under a temporary NSManagedObjectContext, whose parent is the mainQueueManagedObjectContext.
A quick aside on re-fetching in RestKit:
If the request were composed entirely of NSManagedObject instances, the resulting objects would be "refetched" using (I think) the mainQueueManagedObjectContext. (IIRC, once all the object mapping is complete, the objects and sub-objects would all be re-fetched from the main MOC, replacing the objects that were created on the temporary MOC used for mapping.)
This refetching is done (as far as I can tell) because when a MOC gets dealloc'd, all of the managed objects that it manages become invalid. So we refetch the objects in a MOC to which myObjectManager.managedObjectStore maintains a strong reference, so that they remain valid after the temporary MOC goes away.
Because the root object in the mapping is a POOCO, any references it has to managed objects do not get refetched, so they remain attached to the temporary MOC, and become invalid once mapping is complete.
Has anyone else run into issues like this? Are there best practices you can suggest? Is it possible to tell RestKit, "refetch these managed objects using the mainQueueManagedObjectContext" or anything like that?
Managed object instances can't be passed between threads, and RestKit does the mapping on background threads. If the returned objects are managed objects then they will be transferred to the main thread for you. It could be argued that nested managed objects not being switched to the main thread for you is a bug / oversight in RestKit (that you could raise as an issue in github).
You can ask RestKit to use the main thread for some things (by explicitly creating the operations and running them, though this doesn't cover everything) but you really don't want to do that.
So, your main option is to have a method in your container object which you can call in the success block and which iterates through the contained managed objects and 'refetches' them - as you know that all the objects exist this can be done with objectWithID:, and the success block is called on the main thread.
The question actually has some misconceptions about when RestKit performs refetching. Under the circumstances described in the question, RestKit actually does return an instance of RKRefetchingMappingResult in the success block. Calling any of the accessors on that object (such as array or firstObject) actually does perform the refetching. The mistake was that I was never invoking any of these methods on the RKMappingResult.
My assumption was that, since the object I was sending in the PUT request would become the operation's targetObject, the object would be updated in-place by RestKit. This is why I never felt the need to do anything with the RKMappingResult. The assumption isn't technically wrong, but by accessing the object directly once the mapping was complete instead of using the RKMappingResult, I was skipping the refetching step.
I've proposed a change in this behaviour in an issue I've filed with the RestKit folks, to make refetching automatic in certain circumstances.
To summarize:
When making a request using RKManagedObjectRequestOperation (whether you specify that manually or RestKit selects this class for you), always make sure you exercise the RKMappingResult to ensure that the results are re-fetched from the correct NSManagedObjectContext.

Can I "manually" insert items into an AFIncrementalStore's cache?

I have set up AFIncrementalStore to grab objects from a JSON service over the network and set its persistentStore to be an SQLite database. This all works fine.
Now what I want to do is add objects to that SQLite database out-of-band (from something other than the web service the AFIncrementalStore is pointing to), and have those additions reflected in the fetched results controllers created from the original managed object context.
I've created a managed object context with the original MOC as its parent and I can add objects to that and they're seen by the fetched results controller. But they're not saved to the AFIncrementalStore's SQLite db. Interestingly, AFIncrementalStore is seeing these objects as it was trying to save them back to the JSON service and complaining the correct endpoint didn't exist (I fixed this by overriding requestForInsertedObject:insertedObject to return nil.)
Anyone know how I achieve this?
If you don't want to POST objects to the server you have to override methods
requestForInsertedObject:insertedObject
requestForUpdatedObject:updatedObject
requestForDeletedObject:deletedObject
Then when you call context's save: method your objects must be saved in the database. I'm using similar logic when I'm doing CRUD operations offline and it is working fine.
It sounds like you have already found most of the answer. AFIncrementalStore checks for a nil response from requestForInsertedObject: in your AFRESTClient subclass. If that method returns nil, AFIS creates a permanent ID for the object, stores the object in the backing store and doesn't try to send it to the server again. This is all in the first section of executeSaveChangesRequest:withContext:error:.
Are you always calling save: on the parent ManagedObjectContext? If not, that would be the other reason it's not storing the object in SQLite. But then it shouldn't be trying to POST the object to the server either.

Unsure of how to manage data in ios app

I hope this question isn't too general/ambiguous...
I'm writing an iphone quiz game app and am having trouble figuring out the best way to handle data. Currently I am thinking of having a single Model class that holds an array of "User" classes which each have an array of user-specific "Question" classes. I'd like to be able to access the overarching Model from any of my view controllers, but that means I'll probably have to pass the model object to any new view controller, use a singleton, or do something else. What is the best way to access my Model object from other classes? Another factor I'm not sure about is being able to save the data - would I have to use Core Data/SQLite to save my single Model object, or is there a simpler way?
I'd start by designing a schema using CoreData. IMO, its best to start out using CoreData because then you'll never have to convert your data layer to CoreData, in the event that your app scales beyond a simple object or two.
The other route would be to create a web service that returns your data... so you just call the service and it returns a collection of user objects. You can either send down the entire object graph with the questions, or create another service to return a collection of questions for a specific user. If you have a web server handy, this method scales the best because you don't have to rely on app updates to get new questions into your system. I would still use CoreData to cache the results... so that way you're not downloading the same information all the time.
So when it comes to accessing CoreData objects, I use a repository class that's a singleton. This makes it easy for any view controller to grab an instance of the repository and get some data. Here's what something like that might look like;
[[Repository defaultRepository] findFirst:[User class]
where:#"name == 'John'"]
There's a lot of redundant code to fetch data so wrapping that up in an object will help get all that nasty code, like predicates and sorting, out of your view controllers. You can see where I leverage a va_list in the where clause so I can inject that string right into my predicate. Here are some other methods you could implement:
- (NSArray *) findAll:(Class)entity
sortByKey:(NSString *)key
ascending:(BOOL)ascending;
- (NSArray *) findAll:(Class)entity
sortByKey:(NSString *)key
ascending:(BOOL)ascending
where:(NSString *)format, ...;
- (id) findFirst:(Class)entity
where:(NSString *)format, ...;
I'm not sure if this is the preferred way, but I've had a lot of success with this method. Hope this helps!
Check this link, this will help you a lot
Link: http://mobile.tutsplus.com/tutorials/iphone/iphone-sdk_store-data/
This cover 4 major ways to store data in iPhone with sample code.
1) NSUserDeafult
2) Property Lists
3) SQLLite
4) Core Data

Resources