RestKit: distinguish multiple requests in didLoadResponse: - objective-c

I'd like to use RestKit and handle several different requests in the same class, i.e. in the didLoadResponse: method. How can I distinguish between the different requests? How do I know which request is finished?
I'm doing the request via
RKClient *client = [RKClient sharedClient];
[client get:#"/....", method] delegate:self];
Then, in the delegate-method
- (void)request:(RKRequest *)request didLoadResponse:(RKResponse *)response {
if (???) // request which gets XY returned
...
else if (???) // request which gets YZ returned
...
}
is that possible?

Sure, the RKClient get: method returns a RKRequest object. Just set a userData to the request and retrieve it later in the delegate.
RKClient *client = [RKClient sharedClient];
RKRequest *request = [client get:#"/....", method] delegate:self];
[request setUserData:#"FirstRequest"];
and check it later in the delegate
- (void)request:(RKRequest *)request didLoadResponse:(RKResponse *)response {
id userData = [request userData];
if ([userData isEqual:#"FirstRequest"]) // request which gets XY returned
...
else if (...) // request which gets YZ returned
...
}

This isn't an exact answer to your question, but I have the feeling that some people will come here wondering how to distinguish multiple requests in didLoadObjects, as I did. The solution is to use isKindOfClass.
For example, I make two HTTP calls when a user logs into my app, and I want to distinguish the object returned from the getUser call from the object returned by getSummary (because if I don't then it crashes). This code checks if the returned object is a "kind of" that particular class, and if so sets the object to a local instance of that object.
- (void)objectLoader:(RKObjectLoader*)objectLoader didLoadObjects:(NSArray*)objects {
if ([[objects objectAtIndex:0] isKindOfClass:[APIUser class]]) {
APIUser *apiUser = [objects objectAtIndex:0];
}
else if ([[objects objectAtIndex:0] isKindOfClass:[APIUserSummary class]]) {
APIUserSummary *summary = [objects objectAtIndex:0];
}
}

Related

What exactly +[NSURLProtocol setProperty:forKey:inRequest:] does

NSURLProtocol defines following methods:
/*!
#method propertyForKey:inRequest:
#abstract Returns the property in the given request previously
stored with the given key.
#discussion The purpose of this method is to provide an interface
for protocol implementors to access protocol-specific information
associated with NSURLRequest objects.
#param key The string to use for the property lookup.
#param request The request to use for the property lookup.
#result The property stored with the given key, or nil if no property
had previously been stored with the given key in the given request.
*/
+ (nullable id)propertyForKey:(NSString *)key inRequest:(NSURLRequest *)request;
/*!
#method setProperty:forKey:inRequest:
#abstract Stores the given property in the given request using the
given key.
#discussion The purpose of this method is to provide an interface
for protocol implementors to customize protocol-specific
information associated with NSMutableURLRequest objects.
#param value The property to store.
#param key The string to use for the property storage.
#param request The request in which to store the property.
*/
+ (void)setProperty:(id)value forKey:(NSString *)key inRequest:(NSMutableURLRequest *)request;
/*!
#method removePropertyForKey:inRequest:
#abstract Remove any property stored under the given key
#discussion Like setProperty:forKey:inRequest: above, the purpose of this
method is to give protocol implementors the ability to store
protocol-specific information in an NSURLRequest
#param key The key whose value should be removed
#param request The request to be modified
*/
+ (void)removePropertyForKey:(NSString *)key inRequest:(NSMutableURLRequest *)request;
But, if I trying to associate NSURLAuthenticationChallenge with request, using following code
[NSURLProtocol setProperty:challenge forKey:#"challenge" inRequest:self.request];
I see next error in log:
2016-03-20 18:00:41.252 Web#Work[6084:586155] ERROR: createEncodedCachedResponseAndRequestForXPCTransmission - Invalid protocol-property list - CFURLRequestRef. protoProps=<CFBasicHash 0x7f98ec6dcca0 [0x10684e180]>{type = mutable dict, count = 3,
entries =>
0 : <CFString 0x101f47938 [0x10684e180]>{contents = "challenge"} = <NSURLAuthenticationChallenge: 0x7f98ec4cd700>
1 : <CFString 0x7f98e9fd03a0 [0x10684e180]>{contents = "Accept-Encoding"} = <CFBasicHash 0x7f98ec7c4490 [0x10684e180]>{type = mutable dict, count = 1,
entries =>
2 : <CFString 0x7f98ec78b930 [0x10684e180]>{contents = "Accept-Encoding"} = <CFString 0x106330828 [0x10684e180]>{contents = "gzip, deflate"}
}
2 : <CFString 0x1063310e8 [0x10684e180]>{contents = "kCFURLRequestAllowAllPOSTCaching"} = <CFBoolean 0x10684ebf0 [0x10684e180]>{value = true}
}
From this message, I suspect that only property list supported objects may be associated with NSURLRequest using this API. But I do not completely understand why.
Also, my question is for which purpose this API should be used. How to use it correctly? I want to know what exactly setProperty:forKey:inRequest: does, because it seems to do more work, than simply associate objects with request.
UPD So, minimal verifiable example. Very simple NSURLProtocol subclass:
#import "MyURLProtocol.h"
#define kOurRecursiveRequestFlagProperty #"recursive"
#interface MyURLProtocol ()
#property NSURLConnection *connection;
#end
#implementation MyURLProtocol
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
return ![[[self class] propertyForKey:kOurRecursiveRequestFlagProperty inRequest:request] boolValue];
}
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id <NSURLProtocolClient>)client {
return [super initWithRequest:request cachedResponse:cachedResponse client:client];
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
- (void)startLoading {
NSMutableURLRequest* mutableRequest = [[self request] mutableCopy];
[[self class] setProperty:#YES forKey:kOurRecursiveRequestFlagProperty inRequest:mutableRequest];
// Associate any non-property list object with mutableRequest to reproduce issue.
[[self class] setProperty:[UIApplication sharedApplication] forKey:#"dummyKey" inRequest:mutableRequest];
self.connection = [[NSURLConnection alloc] initWithRequest:mutableRequest delegate:self startImmediately:YES];
}
- (void)stopLoading {
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[[self client] URLProtocolDidFinishLoading:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[[self client] URLProtocol:self didLoadData:data];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse {
return request;
}
#end
UPD2 For now, I see that caching is to blame. Even when I use NSURLCacheStorageNotAllowed, this code still tries to create cache dictionary, and write associated [UIApplication sharedApplication] into it.
2016-03-20 20:52:19.630 WebViewDemo[7102:663895] ADD: failed to create cache dictionary at path=/Users/xxx/Library/Developer/CoreSimulator/Devices/89D7C50F-939B-4360-A19F-4547AE4F7515/data/Containers/Data/Application/6F05411C-0261-4A33-9531-9E4E900C4910/Library/Caches/Test.WebViewDemo. key=0x7fe76af45f90
So, now next my question is - how can I disable this caching without implementing my own associated dictionary. Should I do that, or I simply should avoid setProperty:forKey:inRequest:.
And, most importantly, is that correct to use setProperty:forKey:inRequest: to store kOurRecursiveRequestFlagProperty, as Apple sample suggests? I'm still trying to figure out when I can use this methods, and when I can't.
IIRC, the reason why the data has to be plist-serializable is that in background tasks, URL requests are serialized and sent over XPC to a background daemon that actually performs the URL fetch. If the data weren't serializable, it would get lost in transit.
The objects you store here need to be small, self-contained pieces of data for the purposes of allowing your protocol to identify that request.
Typically, if you need to associate something more complex with a request, you would use this method to store a UUID or some other arbitrary string. Then you would store that same string as a key in a normal NSDictionary within your app, disposing of the dictionary key and associated data after you tell the client that the request has completed/failed.

How can I check if my OS X application can connect to a specific server-- assuming there is internet connection?

I currently am using AFNetworking to determine if my application has network reachability.
NSNumber *s = notification.userInfo[AFNetworkingReachabilityNotificationStatusItem];
AFNetworkReachabilityStatus status = [s integerValue];
if (status == AFNetworkReachabilityStatusReachableViaWWAN || status == AFNetworkReachabilityStatusReachableViaWiFi) {
But, now I also need to know if my application can reach a specific server. More specifically, the server I am connecting to may be down and I need a way to determine if this is the case, from the client side, so I can notify my users appropriately.
It's a very tough google because all searches I do just point me to "How to determine network reachability". Has anybody dealt with this before, and have a solution in mind?
EDIT: #AvT recommended a promising looking solution, so I tried it like this:
self.testTSCReachabilityManager = [AFNetworkReachabilityManager managerForDomain:#"www.asdasfjsldfkjslefjslkjslfs.com"];
__weak MyObject *weakSelf = self;
[self.testReachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
if (weakSelf.testReachabilityManager.reachable) {
NSLog(#"YES");
}
else
{
NSLog(#"NO");
}
}];
But unfortunately, it is logging out "YES" for me, even after I have confirmed it is most definitely not reachable.
Instantiate AFNetworkReachabilityManager with class method
+ (instancetype)managerForDomain:(NSString *)domain;
and pass string with the required domain. AFNetworkReachabilityManager will check reachability of this domain.
If serverURL is an url of your server you should use it the following way:
[AFNetworkReachabilityManager managerForDomain:serverURL.host]
Update
Following code works as expected:
static AFNetworkReachabilityManager *testTSCReachabilityManager;
testTSCReachabilityManager = [AFNetworkReachabilityManager managerForDomain:#"www.asdasfjsldfkjslefjslkjslfs.com"];
[testTSCReachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
if (testTSCReachabilityManager.reachable) {
NSLog(#"YES");
}
else
{
NSLog(#"NO");
}
}];
[testTSCReachabilityManager startMonitoring];
Update: I actually ended up going w/ a different implementation than what Avt recommended, and did what matt recommended in the comments instead
I created an NSURLRequest and make a request to my server, then used the delegate callbacks to determine if the server was reachable. Works like a charm
-(void)checkConnectionToServers
{
NSMutableURLRequest* request = [NSMutableURLRequest new];
request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"www.myserver.com"] cachePolicy:0 timeoutInterval:(NSTimeInterval)5.0];
[request setHTTPMethod:#"GET"];
[NSURLConnection connectionWithRequest:request delegate:self];
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(#"SUCCESS");
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"FAIL");
}

How to do in Restkit a PUT request with no body, url params, and get back an object

Lets say that I have to do this dynamic PUT request:
"http://mydomain.com/api/5?value=66"
The body is empty.
I will get in return a 201 (Created) status, and in the body I'm getting back a json object,
Let's call it MyObject that has fields NSNumber* Id, NSString* name;
Now in restkit I have these options:
- [[RKObjectManager sharedManager] putObject:nil mapResponseWith:MyMapping delegate:self];
MyMapping maps MyObject.
The problem is that if I'm sending nil, it doesn't know the mapping and throws "Unable to find a routable path for object of type '(null)' for HTTP Method 'PUT'"
- [[RKClient sharedClient] put:putUrl params:nil delegate:self];
where putUrl = "http://mydomain.com/api/5?value=66"
The problem here is that there is no mapping for the response so only didLoadResponse is called back and didLoadObjects never called
[objectManager.router routeClass:[MyObject class] toResourcePath:putUrl forMethod:RKRequestMethodPUT];
MyObject *obj = [[MyObject alloc] init];
[[RKObjectManager sharedManager] putObject:obj mapResponseWith:MyMapping delegate:self];
The problem here is that first that I fake it (send MyObject as param while it isn't) and it works only for the first time. for the second time I'm trying to use this method I'm getting this exception: "A route has already been registered for class 'MyObject' and HTTP method 'PUT'"
Any suggestion what to do?
Thanks
If anyone is intersted I found the answer after seeing what restkit is doing.
putUrl = "http://mydomain.com/api/5?value=66";
MyMapping maps the returned MyObject that has fields NSNumber* Id, NSString* name;
Here is the code to get it working:
void (^blockLoader)(RKObjectLoader *);
blockLoader = ^(RKObjectLoader *loader) {
loader.delegate = self;
loader.objectMapping = MyMapping;
};
NSString *resourcePath = putUrl;
[[RKObjectManager sharedManager] sendObject:nil toResourcePath:resourcePath usingBlock:^(RKObjectLoader *loader) {
loader.method = RKRequestMethodPUT;
blockLoader(loader);
}];

How do you return from an asynchronous NSURLConnection to the calling class?

I've got the following class which makes an HTTP post request asynchronously to avoid problems on the main UI thread:
#implementation DataFeeder
-(void) doLookup:(NSString *)inputValue
{
NSString *myRequestString = [NSString stringWithFormat:#"val=%#", inputValue];
NSMutableData *myRequestData = [ NSMutableData dataWithBytes: [ myRequestString UTF8String ] length: [ myRequestString length ] ];
NSURL * myUrl = [NSURL URLWithString: #"http://mywebsite/results.php"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL: myUrl];
[request setHTTPMethod: #"POST"];
[request setHTTPBody: myRequestData];
[request setTimeoutInterval:10.0];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
responseData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[responseData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// Show error message
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// Use responseData
// Got all my response data here, so build up an object ready to send back
}
#end
I'm invoking the above from my ViewController using the following line of code :
MyObject * myObj = [feeder doLookup:#"SomeStaticStringForNow"];
So, this is how I understand it :
The doLookup will execute the request on an asynchronous
connection.
When the data has been fully loaded, it will invoke connectionDidFinishLoading
Once the data has finished loading, I will build up an object from the response data that I will send back to the calling controller
How can I have the calling controller listen out for this? Do I need to implement my own callback methods in the ViewController that will listen out for invocation and then stop a spinner and update the UI based on the contents of myObj ?
I'm hoping theres a really easy way that I've overlooked...
Thanks
Yeah, you should implement your callback using the delegate pattern. That is, in my opinion, the easiest and most standard way to do it. There are other ways, as you can see in the other responses.
In your DataFeeder.h file:
#protocol DataFeederDelegate
- (void)dataReady:(NSData*)data;
#end
#interface DataFeeder : NSObject {
id delegate_;
}
- (id)initWithDelegate:(id<DataFeederDelegate>)delegate;
#end
In your DataFeeder.m:
#implementation DataFeeder
- (id)initWithDelegate:(id<DataFeederDelegate>)delegate {
self = [super init];
if(self) {
delegate_ = delegate;
}
return self;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[delegate_ dataReady:responseData];
}
#end
You would instantiate a DataFeeder object like this:
DataFeeder *dataFeeder = [[DataFeeder alloc] initWithDelegate:self];
Of course, the calling view controller has to implement the DataFeederDelegate methods.
You have several approaches to getting you ViewController notified when the data is there:
define a delegate protocol between the ViewController and the DataFeeder, so that the latter sends a message to the former in connectionDidFinishLoading:;
use NSNotificationCenter so to decouple DataFeeder and ViewController: ViewController adds itself as an observer to the default notification center:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dataIsThere:) name:kMyNetworkNotificationDataIsThere object:nil];
while DataFeeder send the notification at the right time:
[[NSNotificationCenter defaultCenter] postNotificationName:kMyNetworkNotificationDataIsThere object:self];
make ViewController implement the delegate methods for NSURLConnection and handle the response itself (this will require passing it as a parameter to DataFeeder constructor).
A nice way to do this would be using blocks. Your doLookup: method could accept a block object, and you can invoke that when the connection finishes.
Since you probably want to be able to perform multiple lookups with different completion blocks, you need to associate the passed in block with the appropriate NSURLConnection.
To do this ou can either use an NSURLConnection subclass with a completionBlock property, or use objective-C associated objects (by using the objc_setAssociatedObject and objc_getAssociatedObject functions) to attach the block to the connection object.
When everything is ready in the connectionDidFinishLoading: method and you've prepared the final response object, you grab the block from the NSURLConnection object and invoke it, passing it the final data.
So you eventually want your client code to look like this:
[feeder doLookup:#"Something" completionBlock:(FetchedData *data, NSError *error) {
if (error) {
// ...
return;
}
// access the returned data
}];
I hope this was enough detail for you.

Access Request Data sent, inside requestFailed Function ASIHttpRequest

How to access the (POST)data sent with the request from the requestFailed/requestFinished function.
- (void) abc {
NSString *postString = #"john";
NSURL *url = [NSURL URLWithString:#"http://abc.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setPostValue:postString forKey:#"name"];
[request setDelegate:self];
[request startAsynchronus];
}
- (void) requestFinished:(ASIHTTPRequest *)request
{
// Question is whether the request holds the sent post values.
// If it holds. how can we access them.
// i tried using [request valueForKey:#"name"];
// but it won't work.
}
Handling success and failure for multiple requests in delegate methods
If you need to handle success and failure on many different types of
request, you have several options:
If your requests are all of the same broad type, but you want to
distinguish between them, you can set the userInfo NSDictionary
property of each request with your own custom data that you can read
in your finished / failed delegate methods. For simpler cases, you can
set the request’s tag property instead. Both of these properties are
for your own use, and are not sent to the server.
If you need to handle success and failure in a completely different way for each
request, set a different setDidFinishSelector / setDidFailSelector for
each request For more complex situations, or where you want to parse
the response in the background, create a minimal subclass of
ASIHTTPRequest for each type of request, and override requestFinished:
and failWithError:.
That provided me a good solution to handle different requests.
You could try this -
- (void)requestFinished:(ASIHTTPRequest *)request {
NSLog(#"Response %d ==> %#", request.responseStatusCode, [request responseString]);
}
You can also handle other methods if you choose, such as:
- (void)requestStarted:(ASIHTTPRequest *)request;
- (void)requestFailed:(ASIHTTPRequest *)request;
The docs are located at http://allseeing-i.com/ASIHTTPRequest/ and are fantastic.
You can cast your request into a ASIFormDataRequest:
if ([request isKindOfClass:[ASIFormDataRequest class]]) {
ASIFormDataRequest *requestWithPostDatas = (ASIFormDataRequest *)request;
NSArray *myPostData = [requestWithPostDatas getPostData];
}
You will also have to make "postData" accessible with a "getPostData" public function in ASIFormDataRequest.