RestKit GET query parameters - iphone

I've been using RestKit 0.10.0 for a while now and up until this point, I only posted serialized objects to my server:
[[RKObjectManager sharedManager] postObject:serializedObject
usingBlock:^(RKObjectLoader *loader) {
loader.delegate = self;
loader.objectMapping = responseMapping;
loader.serializationMIMEType = RKMIMETypeFormURLEncoded;
loader.targetObject = nil;
}];
So far, so good. But I now need to make a GET request to the server with a few query parameters. The first natural thing that came in mind was to do the same as I did for posting objects:
create a serialization mapping for the object encapsulating the query parameters
create a response mapping for the object being received from the server
define and use a router for RKRequestMethodGET (instead of RKRequestMethodPOST)
make the request using getObject:usingBlock (instead of postObject:usingBlock)
I soon found out this is not the way to do it, so after searching the available resources (RestKit Wiki, RestKit Google group) I now know of two solutions considered as valid:
Appending the query parameters to the resource path.
This works perfectly.
NSDictionary *queryParams = [NSDictionary dictionaryWithObjectsAndKeys:
token, #"accessToken",
[NSNumber numberWithInt:level], #"level",
[NSNumber numberWithInt:count], #"count",
nil];
NSString* resourcePath = [PEER_SUGGESTIONS_CONTROLLER_PATH stringByAppendingQueryParameters:queryParams];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:resourcePath
usingBlock:^(RKObjectLoader *loader) {
loader.delegate = self;
loader.objectMapping = responseMapping;
}];
Setting the query parameters in the loader block.
This does not send the query parameters.
RKParams *params = [RKParams params];
[params setValue:token forParam:#"accessToken"];
[params setValue:[NSNumber numberWithInt:level] forParam:#"level"];
[params setValue:[NSNumber numberWithInt:count] forParam:#"count"];
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:PEER_SUGGESTIONS_CONTROLLER_PATH
usingBlock:^(RKObjectLoader *loader) {
loader.delegate = self;
loader.objectMapping = responseMapping;
loader.params = params;
}];
My questions are:
Why doesn't the second solution work?
Why is the first solution working without having to set the loader.targetObject to nil, although I do not have any root key path in the JSON response?
What are the cases where I should use the getObject:usingBlock method? What is its intended purpose?
What should I use loader.params for? The object mapping tutorial from the wiki says this property can be used to encapsulate POST parameters, but I do not see the point since I can wrap the parameters in the serialized object that is being sent with the method postObject:usingBlock.
Thanks.
[LATER EDIT]
Regarding the answer to my second question: I've been setting the targetObject to nil in the loader block when making POST requests beacause otherwise RestKit will try use the send object mapping for the response (check this link for a related discussion). But since I am using loadObjectsAtResourcePath:usingBlock:, there is no object being sent, therefore the response will naturally map on the response mapping without having to the set targetObject to nil.

Why doesn't the second solution work?
params is used to create a HTTP body, which is not used in a GET/HEAD request.
Why is the first solution working without having to set the loader.targetObject to nil, although I do not have any root key path
in the JSON response?
I think targetObject is nil by default. You normally don't set it, the request will create it if needed. The only time I use it is when requesting objects without primary keys or other weird problems.
What are the cases where I should use the getObject:usingBlock method? What is its intended purpose?
This is a convenience method so you don't have to remember all the correct syntax. Internally it just sends an object load request using GET.
EDIT:
Use this if you have an object you want to update.
What should I use loader.params for? The object mapping tutorial from the wiki says this property can be used to encapsulate POST
parameters, but I do not see the point since I can wrap the parameters
in the serialized object that is being sent with the method
postObject:usingBlock.
Whatever you put in params will be serialized to an HTTP body (or body stream). Again, postObject:usingBlock: is just a convenience method so you don't have to remember everything.
RestKit is open source. If you are not sure how it works you are free to follow the parameters internally. If you app and web service is well designed, you should be able to use the convenience methods. Sometimes you can not, and then you can use the raw forms like you have done.
EDIT:
Q Hrm, quoting your bullet points messed up the numbers...

I solved adding a Category to RKObjectLoader, that is:
for method
-(void)getObject:(id<NSObject>)object usingBlock:(RKObjectLoaderBlock)block;
I added into the Category a modified method:
-(void)getObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters usingBlock:(void(^)(RKObjectLoader *))block;
Here it is the listing fpr file "RKObjectManager+QueryParameters":
//
// RKObjectManager+QueryParameters.h
// AlphaClient
//
// Created by Antonio Rossi on 14/07/12.
//
#import <RestKit/RestKit.h>
#interface RKObjectManager (QueryParameters)
- (void)getObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters usingBlock:(void(^)(RKObjectLoader *))block;
- (void)sendObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters method:(RKRequestMethod)method usingBlock:(void(^)(RKObjectLoader *))block;
#end
Here is the listing for file "RKObjectManager+QueryParameters.m":
//
// RKObjectManager+QueryParameters.m
// AlphaClient
//
// Created by Antonio Rossi on 14/07/12.
//
#import "RKObjectManager+QueryParameters.h"
#implementation RKObjectManager (QueryParameters)
- (void)getObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters usingBlock:(void(^)(RKObjectLoader *loader))block {
[self sendObject:object queryParameters:queryParameters method:RKRequestMethodGET usingBlock:block];
}
- (void)sendObject:(id<NSObject>)object queryParameters:(NSDictionary*)queryParameters method:(RKRequestMethod)method usingBlock:(void(^)(RKObjectLoader *))block {
NSString *resourcePath = [self.router resourcePathForObject:object method:method];
[self sendObject:object toResourcePath:resourcePath usingBlock:^(RKObjectLoader *loader) {
loader.method = method;
// need to transform the original URL because when method is GET the additional paramentes don't get added
RKURL *originalBaseURL = [RKURL URLWithBaseURL:[loader.URL baseURL]];
NSString *resourcePath = [self.router resourcePathForObject:object method:RKRequestMethodGET];
RKURL *authTokenURL = [originalBaseURL URLByAppendingResourcePath:resourcePath queryParameters:queryParameters];
[loader setURL:authTokenURL];
block(loader);
}];
}
#end
One more step is to add #import "RKObjectManager+QueryParameters.h" in your implementation file.
In this new method it is assumed that the router property of RKObjectManager has been defined before making a call to it.

Related

Check if the current request is already in request queue using restkit iphone

I am using restkit for sending request, response mapping and all. but sometimes, i am sending same request multiple time which comes from different screens. So it get added to request queue. But i don't want to add request into request queue if the request is already there. How can i check that.
I am calling function like this
//Here I need to check if the following url is in RKRequestQueue. If it not there then call the below method,
[self getDataFromServer];
- (void)getDataFromServer{
RKObjectManager *manager = [[RestKit sharedDataManager] objectManager];
[manager loadObjectsAtResourcePath:#"/getData" usingBlock:^(RKObjectLoader *loader) {
[RKClient sharedClient].requestQueue.showsNetworkActivityIndicatorWhenBusy = YES;
loader.method = RKRequestMethodPOST;
loader.params = inputData;
loader.onDidFailWithError = ^(NSError *error) {
};
loader.onDidLoadObjects = ^(NSArray *objects) {
};
}
Any help is appreciated.
Interesting question, I was pretty sure that you can actually check the URLs of your requests in the RKRequestQueue, but I wasn't able to find anything useful for that in the RKRequestQueue reference. Instead, I've found the containsRequest: method, but this will only compare RKRequest objects, not the actual URLs.
I guess the simplest thing to do is to create some kind of proxy for managing your network activity, implement the requestQueue:didSendRequest: delegate method and monitor which URLs are currently processed.

RestKit Customize Serialization

I need to configure RestKit to interact with a server API that expects the following:
All requests are in multipart/form-data
There is a JSON payload. However the client must collapse the payload
into a form-data string and prepend it with "json="
Authentication is done via hashing the contents of JSON payload with
the URL including any GET parameters. And appending this hash to URL (Yes I know this is bad. But I
have no control over the API)
I'm trying to get RestKit append its serialization output to a json=, then modify the RKObjectLoader to computer and append the correct signature hash.
I've tried subclassing RKObjectManager and overriding sendObject:toResourcePath:usingBlock: to override the HTTPBody; but can't get this to work. I've also tried subclassing RKClient's and configureRequest: but it seems like this method gets called before object serialization.
This is my first day working with RestKit. Help would be much appreciated! Thanks!
Update: I've worked out a different solution than my original described bottom. I've created a subclasses of RKObjectLoader and RKObjectManager. The custom object manager simply has one change to return the custom object loader. The object loader's send message was overridden to perform custom construction.
Here's a solution I've worked out.
I use a subclass of RKClient where I override
- (void)configureRequest:(RKRequest *)request {
[super configureRequest:request];
request.delegate = self;
}
And do all my encoding/signing in the delegate.
- (void)requestWillPrepareForSend:(RKRequest *)request {
[self encodeJSONPayloadForRequest:request];
[self signRequest:request];
}
- (void)encodeJSONPayloadForRequest:(RKRequest*)request {
NSDictionary * bodyDict = (NSDictionary*)request.params;
NSError * error = nil;
NSString * jsonString = [bodyDict JSONStringWithOptions:JKSerializeOptionNone error:&error];
if (error) NSAssert(false, #"error serializing into JSON");
NSDictionary * dictionary = [NSDictionary dictionaryWithObject:jsonString forKey:#"json"];
request.params = dictionary;
}
I'll need to implement a mechanism for passing through to any pre-existing delegate. However, this approach seems to do the trick.

AFNetworking HTTPClient subclass with XMLParser

I am writing a small iOS app that queries a XML REST webservice. The networking framework in use is AFNetworking.
Situation
To query the webservice I subclassed AFHTTPClient:
#interface MyApiClient : AFHTTPClient
and in the implementation I make that available as a singleton:
+ (MyApiClient *)sharedClient {
static MySharedClient *_sharedClient = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedClient = [[self alloc] initWithBaseUrl:[NSUrl URLWithString:#"http://url.to.the.webservice"]];
});
return self;
}
and in initWithBaseURL I tell AFNetworking to expect XML content:
[self registerHTTPOperationClass:[AFXMLRequestOperation class]];
Now I can call getPatch on the singleton from my ViewController and in the success block start parsing my returned XML. In NSXMLParserDelegate methods in the ViewController I can then pick the parts of the XML I am interested in and do stuff with it.
Problem
I want to have methods in my HTTPClient singleton that handle everything related to the webservice and return data models or list of models instead of XML.
For example I want to do something like this:
ServerModel *status = [[MyApiClient sharedClient] getServerStatus];
The ApiClient would then internally call the webservice, parse the XML and return the model.
How can I do that? Normally I would use a delegate that gets called once the XML is parsed, but due to the singleton nature of the ApiClient there could be multiple delegates?
Hope someone can shed light on this, thanks!
Use blocks instead of delegates.
From my ApiClient class:
- (void)getPath:(NSString *)path
parameters:(NSDictionary *)parameters
success:(void (^)(id response))success
failure:(void (^)(NSError *error))failure
{
NSURLRequest *request = [self requestWithMethod:#"GET" path:path parameters:parameters];
[self enqueueHTTPOperationWithRequest:request success:success failure:failure];
}
-(void)fetchAllUsersSuccess:(void (^)(id))success
failure:(void (^)(NSError *))failure
{
[self getPath:#"/api/mobile/user/"
parameters:nil
success:^(id response) {
if([response isKindOfClass:[NSXMLParser class]]){
//parse here to new dict
success(newDict);
} else
success(response);
} failure:^(NSError *error) {
failure(error);
}];
}
Now I can use it like:
ServiceApiClient *apiClient = [ServiceApiClient sharedClient];
[apiClient fetchAllUsersSuccess:^(id dict) {
for (NSDictionary *object in [dict objectForKey:#"objects"]) {
[ServiceUser addUserFromDictionary:object
inContext:self.managedObjectContext];
}
NSError *error= nil;
[self.managedObjectContext save:&error];
if (error) {
NSLog(#"%#", error);
}
} failure:^(NSError * error) {
NSLog(#"%#", error);
}];
(Apologies in advance for this "sort-of" answer, but we're working towards a better solution...)
You need to take a step back and think about your design carefully.
You're having problems because you've got an idea that something in your design needs to be a singleton, but either:
1) that's not actually necessary,
2) something might already exist that does that job for you (e.g. the HTTP lib you're using),
or
3) You're making the wrong thing a singleton, or you haven't portioned out your design into the appropriate parts to work well with the singleton idea
So, can you tell me explicitly why you're going for a singleton approach? Is it just to ensure that only one network request can happen at once? Is there any notion of statefulness in your singleton object? Then I'll update this answer or comment, etc.
(Digression: I would also add that in some cases there might be a true need for a 'strong' singleton -- by which I mean that there really is only one possible instance, and that mechanism is baked right into your object, as you are doing - but this isn't it. The alternative is a 'weak' singleton, by which I mean your core object that actually does the work has a plain init method as usual, but shared access to a common object goes via another object, which is a kind of simple 'factory' that instantiates/holds the shared instance. The advantage of this weak singleton idea is that your code is more re-usable in different contexts - e.g. you could decide to do multiple HTTP requests/sessions concurrently at a later time - and it sometimes makes writing tests less problematic).

RestKit - Load a simple array

I'm using RestKit into my iPhone application to load a list of countries. The problem is the elementToPropertyMappings method uses a dictionary to map each object. In my case I have an array of strings that I'd like to map to the name property on my Country class.
Anyone know how todo this?
elementToPropertyMappings
Must return a dictionary containing
mapping from JSON element names to
property accessors
(NSDictionary *)elementToPropertyMappings Declared In RKObjectMappable.h
My JSON Data
["Argentina","Australia","Austria","Belgium","Bolivia","Brazil","Bulgaria","Canada","Cayman Islands","China","Costa Rica","Croatia","Czech Republic","Denmark","Ecuador","Ethiopia","F.Y.R.O. Macedonia","Finland","France","French Polynesia","Germany","Guam","Hong Kong SAR","Indonesia","Ireland","Israel","Italy","Japan","Latvia","Lithuania","Luxembourg","Malaysia","Malta","Mexico","Morocco","Netherlands","New Zealand","Nicaragua","Norway","Papua New Guinea","Peru","Poland","Portugal","Puerto Rico","Qatar","Romania","Russia","Singapore","Slovakia","Slovenia","South Africa","South Korea","Spain","Sweden","Switzerland","Taiwan","United Arab Emirates","United Kingdom","United States","Venezuela","Vietnam"]
UPDATE:
I figured out how to use the RKClient to make a request so the Mapping functionality is skipped. Now I need to figure out what class to use for JSON parsing. The yajl-objc parser looks great but I don't want to include another parser if it can be done with a lib from RestKit.
-(void)loadLocations
{
NSLog(#"loadLocations");
RKObjectManager *objectManager = [RKObjectManager sharedManager];
[[RKClient sharedClient] get:#"/locations/countries.json" delegate:self];
}
- (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response {
NSLog(#"Loaded payload: %#", [response bodyAsString]);
// HOW CAN I PARSE THIS STRING INTO AN NSArray?
}
Figuring out the proper import for RKJSONParser was the most challenging thing for me.
If there is another way to accomplish this with the Mapping classes please let me know.
Here is the code involved with loading a simple array.
#import <RestKit/Support/RKJSONParser.h>
#implementation CountriesViewController
#synthesize countries;
-(void)loadLocations
{
NSLog(#"loadLocations");
[[RKClient sharedClient] get:#"/locations/countries.json" delegate:self];
}
- (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response {
NSLog(#"Loaded payload: %#", [response bodyAsString]);
RKJSONParser* parser = [RKJSONParser new];
countries = [parser objectFromString:[response bodyAsString]];
}
Support for array of strings was added on v0.10: Source

convert Core Data NSManagedObject in to JSON on iPhone?

I was to post some of my COre Data objects back to a web service and would like to send them as JSON. I am receiving objects from the server a JSON using this library:
http://code.google.com/p/json-framework/
But I cannot figure out how to change my objects back to JSON?
To create json from you r objects, you have to build an NSDictionary from your object, and then convert to string with the SBJsonWriter class.
NSDictionary *jsonDictionary = [NSDictionary dictionaryWithObject:(NSArray *)YourArrayOfElements forKey:#"objects"];
SBJsonWriter *jsonWriter = [SBJsonWriter new];
//Just for error tracing
jsonWriter.humanReadable = YES;
NSString *json = [jsonWriter stringWithObject:jsonDictionary];
if (!json){
NSLog(#"-JSONRepresentation failed. Error trace is: %#", [jsonWriter errorTrace]);
}
[jsonWriter release];
NSData *data = [json dataUsingEncoding:NSUTF8StringEncoding];
And then you can set as your post request's body.
If you would like a more full-featured solution that what is offered by a standalone parsing library, you may want to take a look at RestKit: http://restkit.org/
The framework wraps the operations of fetching, parsing, and mapping JSON payloads into objects. It also allows you to update remote representations by POST/PUT'ing the objects back with a request. By default, outbound requests are form-encoded but the library ships with a class for using JSON as the wire format for posting back to the server.
At a high level, here's what your fetch & post operations would feel like in RestKit:
- (void)loadObjects {
[[RKObjectManager sharedManager] loadObjectsAtResourcePath:[#"/path/to/stuff.json" delegate:self];
}
- (void)objectLoader:(RKObjectLoader*)loader didLoadObjects:(NSArray*)objects {
NSLog(#"These are my JSON decoded, mapped objects: %#", objects);
// Mutate and PUT the changes back to the server
MyObject* anObject = [objects objectAtIndex:0];
anObject.name = #"This is the new name!";
[[RKObjectManager sharedManager] putObject:anObject delegate:self];
}
The framework takes care of the JSON parsing/encoding on a background thread and let's you declare how attributes in the JSON map to properties on your object. Mapping to Core Data backed classes is fully supported.