How can I have multiple URL requests to fetch a single type of entity in AFIncrementalStore? - afnetworking

Let's suppose I have a Core Data model using AFIncrementalStore, and I have multiple REST API endpoints for retrieving a list of objects of that model. I can override -requestForFetchRequest:withContext: in AFHTTPClient like so:
- (NSURLRequest *)requestForFetchRequest:(NSFetchRequest *)fetchRequest
withContext:(NSManagedObjectContext *)context {
NSMutableURLRequest *mutableURLRequest = nil;
if ([fetchRequest.entityName isEqualToString:#"Post"]) {
mutableURLRequest = [self requestWithMethod:#"GET" path:#"/posts/foo" parameters:nil];
}
return mutableURLRequest;
}
In this snippet, I retrieve Post objects at /posts/foo, but I also need to retrieve another set from /posts/bar.
How can I do this? The only solution I see is to make two models: one for foo and one for bar, but repeating yourself is lame, and there may be many more API endpoints that get Post objects for me that I'll need to support. Is there some other approach that I'm missing?

You need to inspect fetchRequest more closely than just looking at entityName. You can also look at fetchRequest.propertiesToFetch or possibly other things depending on your data model. You'll still need to send two requests, so just make sure your AFNetworking subclass can tell the difference.
Also: it sounds like your requestForFetchRequest:withContext: method might get really big. You might want to consider a more generic pattern in which you get your NSManagedObject subclass, and ask that to return a fetch request.

Related

Maintaining RKObjectManager/AFHTTPClient request ordering

I've got an iOS app that uses RestKit with Core Data persistence. It loads its data from a few different endpoints, most of which return complete object graphs as nested JSON objects. But one of the endpoints returns objects that contain foreign key references (not nested JSON) to another endpoint's data.
In other words (abbreviated):
[postMapping addAttributeMappingsFromArray:#[ #"postID", ... ]];
// ...
[commentMapping addAttributeMappingsFromArray:#[ ... ]];
[commentMapping addConnectionForRelationship:#"post" connectedBy:#"postID"];
where the relevant Post and Comment entities are set up with the appropriate relationships and so forth.
This all works exactly as it should but only if the request to fetch posts finishes before the request to fetch comments. Otherwise a comment won't be connected to its post, because the post hasn't been fetched and mapped yet.
Right now, I'm getting around it by just not fetching the comments until the posts have been fetched. Something like
- (void)fetchPosts {
[objectManager getObjectsAtPath:#"/posts.json"
parameters:nil
success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
[self fetchComments]; // *now* we can fetch comments
// ...
But I'm wondering if there's a better way?
I figure I might be able to do something like
[objectManager.HTTPClient.operationQueue setMaxConcurrentOperationCount:1];
but most of the time, I do want concurrent requests. It's only two of the requests that must be sequential.
I guess I could set up two different object managers (one that does sequential requests and one that does concurrent ones), but I'm not sure that makes sense.
So, is there are way to specify that two specific -getObjectsAtPath:... requests should be handled sequentially, while all other can run whenever?
Look at creating another request descriptor which drills down into the comment details and processes only the postID attributes. For each one, run the postMapping to create a stub object.
Note that you might still run into race conditions with 2 different background contexts both creating the same objects at the same time, so you may need to handle merge issues (this is if you have multiple object managers as you talked about multiple endpoints).

What's the best way to implement caching behavior for the app using RestKit

I need a help in determining the most appropriate and convenient way to implement caching in my app, so that it would provide the behavior described below.
The app needs to display a list of objects A. It displays objects from cache, while simultaneously requesting the updated version of the list from server. If request is successful, cached objects are completely replaced by the returned ones. The list presented to the user is then refreshed.
Right now I'm achieving this behavior by having a dedicated singleton of a class called DataManager, which incapsulates one instance of ApiClient (that sends requests to server) and one instance of CacheProvider (which is basically a set of convenient methods for accessing Core Data objects). Each method in DataManager looks something like this:
- (NSArray *)objectsAWithUpdatedBlock:(void (^)(NSArray *updatedObjects))updatedBlock
{
[_apiClient requestObjectsAWithCompletionBlock:^(NSArray *objects, NSError *error) {
if (error == nil) {
[_cacheProvider replaceCachedObjectsAWithObjects:objects];
updatedBlock([_cacheProvider cachedObjectsA]);
}
}];
return [_cacheProvider cachedObjectsA];
}
But I have a feeling that I am reinventing the wheel and there must be some standard ways to get the same behavior with RestKit. Am I right or should I continue using my approach?
I'd use an NSFetchedResultsController to get the results to display. It doesn't care if they're cached or not, it just uses what is in the data store and observes changes there (so you don't need your own callback blocks).
RestKit offers a feature called fetch request blocks, which isn't a great name necessarily, but you use it to delete orphaned objects from the data store - i.e. all of your old cached objects.
Using these 2 approaches will likely massively reduce the amount of custom code you need to write.

Structuring a basic app iOS app - single view - best practice

I am currently working on a single view app that does the following:
Grabs users location using CoreLocation
Sends their lon/lat to an API
Returns tide data as JSON
Dive in to objects and keys within the JSON to return: tide levels, locations, etc and display them via text labels.
I have the majority of this working, but it is more so crammed in to viewDidLoad - I am trying to see the best way to organize something like this up. Would I break it in to different methods such as:
setTideData
displayTideData
Or would it be more broken down than that? (And yes im sure it all depends on the details too). I would be displaying probably 8-10 different stats on the view.
Things like
On viewDidLoad do I grab the users location or do it prior on viewDidAppear, etc
Would I then call my displayTideData method inside of viewDidLoad
Just looking for some type of general best practice, was trying to scan for some items but nothing was what I was looking for.
EDIT: Here is an example of my returned data -- https://gist.github.com/ryancoughlin/8043604
Any ideas?
Thanks
This is pretty much a matter of preference and style, but you could simplify your logic in a number of ways by separating certain parts into their own classes, eg:
You could use something like JSON model to deserialize the data into a class, and access it in a more convenient way
Loading the data and deserializing could be handled in a different class that would only return the object with the data you need
Etc.
It's hard to give a better advice without reviewing your code directly, but this should give you some pointers how to simplify things.
EDIT: A bit of code example how to separate the the deserialization and data handling into a custom object:
- (id)initWithJSON:(NSDictionary *)JSON
{
self = [super init];
if (self) {
self.gender = [JSON objectForKey:#"gender"];
self.email = [JSON objectForKey:#"email"];
self.firstName = [JSON objectForKey:#"first_name"];
self.lastName = [JSON objectForKey:#"last_name"];
self.personId = [[JSON objectForKey:#"person_id"] integerValue];
}
return self;
}
You would then simply create an object with the data like this:
Person *person = [[Person alloc] initWithJSON:JSON];
and simply access whatever data you need in a cleaner way:
self.myLabel.text = [NSString stringWithFormat:#"Name: %#", person.firstName];
It sounds like you have a Massive View Controller, which is a bad thing. Try to reorganise your code so you have a fat model and a skinny controller. Controllers should only be responsible for creating and refreshing the view, bootstrapping the model and updating the model based on user interaction. Some possible model objects:
Fetching data from the server.
Parser for converting JSON objects into typed objects (thus encapsulating the knowledge of the JSON structure).
A simple interface (e.g. one object) for your view controller to pull data from and push data to.
Your model should contain all the business logic for the app.

using RestKit un-restfully

Is there any way to use RestKit in an unrestful way?
For example, If I set up an object mapping as such:
[manager.router routeClass:[BBMediaResourceCreate class]
toResourcePath:#"/mediaresources"
forMethod:RKRequestMethodPOST];
RestKit will expect that I post a BBMediaResourceCreate object and receive one back also.
My API however, for reasons I won't go into, is not RESTful compliant in some situations. Rather than receive the newly created resource, I'll get something more like:
{ Model: { Success:true} }
or something similar
Is there a way to map a RestKit post to post a resource of one type but expect a response of another type?
Thanks.
When using v0.10 you can simply set resourcePath:#"/"and respond to
- (void)objectLoaderDidLoadUnexpectedResponse:(RKObjectLoader *)objectLoader
or
- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObject:(id)object
and handle the response in [objectLoader response] like you want. Keep in mind that posting to that resource then needs an explicit, manually set path.

RestKit, Core Data, and Relationship problems. Parent object not recognizing child set

I am trying to map a relationship between Articles and the Photos that belong to them using RestKit. Both objects get stored properly when requesting the resource, but it seems the relationship does not persist. In fact, the Article model seems to not even respond to the Photos selector (This may be the 'duh' spot, but I will provide full code to be through).
I've provided all code in a gist, as I find it easier to look through and format then on StackOverflow. Sorry if this is actually an inconvenience.
https://gist.github.com/3733334
And here is the image of the core data model and the relationships set up (sorry, I had to combine them since I can only post 2 hyperlinks currently):
http://imageshack.us/a/img33/5039/stackoverflowissue.jpg
Everything seems to be working properlly except the relationship between the objects when I try to access photos via anArticle.photos. The selector is unrecognized. I set up a convience method in the Photo model to return all photos with a matching article ID, but I feel this is an un-ideal solution as it sort of removes the whole idea of creating a relationship.
I feel there may be something simple I am missing and any help would be greatly appreciated.
Thanks!
So of course it was a "Duh" error. After some help from a local developer, he pointed out that my Photos variable in my Article.h file was an NSArray, and needed to be changed to an NSSet to store objects mapped by RestKit.
Theres some inconsistency between different versions of RestKit. If you are using the latest one mappings should be set up as shown here: https://github.com/RestKit/RestKit/wiki/Object-mapping. If you want to use entity classes for model specific methods make categories on your NSManagedObjects so that when you change your data model you can regenerate them (Do this only after you extract your methods to a category! Select an entity in your .xcdatamodeld and go to Editor -> Create NSManagedObject Subclass...).
I moved my mappings to the controller that is responsible for syncing with the remote API.
This shuld be helpful too: http://andriyadi.me/logging-in-restkit/.
Also Core Data guidelines stress that you should set up inverse relations (but it's not obligatory).
Fetching entities can also be done better in my opinion. In my project I have an NSObject subclass singleton that (among some other Core Data convenience functionalities) fetches by entity and predicate:
- (NSArray *)fetchEntities:(NSString *)entity usingPredicate:(NSPredicate *)predicate {
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entity];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error];
if (error) {
RLog(#"Error fetching entity %# using predicate %#", entity, predicate);
abort();
}
if ([results count] >= 1) {
return results;
}
return nil;
}
You can alter it to pass predicates as NSStrings too.
Side note:
Currently I'm also struggling with RestKit and object mapping myself :).

Resources