AFNetworking HTTPClient subclass with XMLParser - iphone

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).

Related

How to hook NSURLSession methods with theos?

I created a tweak project using rpetrich's theos and wanted to hook NSURLSession methods but the hooks don't seem to get invoked? Why? This is my Tweak.xm code:
%hook NSURLSession
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSData *data, NSURLResponse *response, NSError *error))completionHandler
{
NSLog(#"testhook dataTaskWithRequest:completionHandler:");
return %orig(request, completionHandler);
}
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
{
NSLog(#"testhook dataTaskWithRequest");
return %orig(request);
}
%end
%hook NSMutableURLRequest
+ (id)requestWithURL:(NSURL *)URL
{
NSLog(#"testhook NSMutableURLRequest");
return %orig(URL);
}
%end
I added the NSMutableURLRequest hook to make sure that the file and the whole tweak was being loaded. I can verify that it does hook requestWithURL: but not any of the NSURLSession methods. I am testing against the code from NSURLSessionExample.
What's missing here? Has anybody successfully hooked NSURLSession?
NSURLSession is a class cluster, and you are hooking the toplevel class that contains no (or scarce little) code.
You should investigate the subclasses of NSURLSession—potentially by logging the real class of an NSURLSession object in-situ. In my limited testing, I received an object whose class was truly named __NSURLSessionLocal.

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.

RestKit GET query parameters

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.

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

NSURLConnection - how to wait for completion

Our iPhone app code currently uses NSURLConnection sendSynchronousRequest and that works fine except we need more visibility into the connection progress and caching so we're moving to an async NSURLConnection.
What's the simplest way to wait for the async code to complete? Wrap it in a NSOperation/NSOperationQueue, performSelector..., or what?
Thanks.
I'm answering this in case anyone else bumps into the issue in the future. Apple's URLCache sample code is a fine example of how this is done. You can find it at:
iOS Developer Library - URLCache
As John points out in the comment above - don't block/wait - notify.
To use NSURLConnection asynchronously you supply a delegate when you init it in initWithRequest:delegate:. The delegate should implement the NSURLConnection delegate methods. NSURLConnection processing takes place on another thread but the delegate methods are called on the thread that started the asynchronous load operation for the associated NSURLConnection object.
Apart from notifications mentioned prior, a common approach is to have the class that needs to know about the URL load finishing set itself as a delegate of the class that's handling the URL callbacks. Then when the URL load is finished the delegate is called and told the load has completed.
Indeed, if you blocked the thread the connection would never go anywhere since it works on the same thread (yes, even if you are using the asynch methods).
I ran into this because our app used NSURLConnection sendSynchronousRequest in quite a few places where it made sense, like having some processing occurring on a background thread occasionally needing extra data to complete the processing. Something like this:
// do some processing
NSData * data = someCachedData;
if (data = nil) {
data = [NSURLConnection sendSynchronousRequest....]
someCachedData = data;
}
// Use data for further processing
If you have something like 3 different places in the same flow that do that, breaking it up into separate functions might not be desirable(or simply not doable if you have a large enough code base).
At some point, we needed to have a delegate for our connections(to do SSL certificate pinning) and I went trolling the internet for solutions and everything was of the form: "just use async and don't fight the framework!". Well, sendSynchronousRequest exists for a reason, this is how to reproduce it with an underlying async connection:
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse *__autoreleasing *)response error:(NSError *__autoreleasing *)error
{
static NSOperationQueue * requestsQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
requestsQueue = [[NSOperationQueue alloc] init];
requestsQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount;
});
NSCondition * waitLock = [NSCondition new];
[waitLock lock];
__block NSError * returnedError;
__block NSURLResponse * returnedResponse;
__block NSData * returnedData;
__block BOOL done = NO;
[NSURLConnection sendAsynchronousRequest:request
queue:requestsQueue
completionHandler:^(NSURLResponse * response, NSData * data, NSError * connectionError){
returnedError = connectionError;
returnedResponse = response;
returnedData = data;
[waitLock lock];
done = YES;
[waitLock signal];
[waitLock unlock];
}];
if (!done) {
[waitLock wait];
}
[waitLock unlock];
*response = returnedResponse;
*error = returnedError;
return returnedData;
}
Posted here in case anyone comes looking as I did.
Note that NSURLConnection sendAsynchrounousRequest can be replaced by whatever way you use to send an async request, like creating an NSURLConnection object with a delegate or something.