RestKit / AFNetworking - Send only one request at a time (queue) - ios

I'm using RestKit / AFNetworking to communicate with a private api. This API is returning a cookie with a sessionID on every call. If you do a call with an already used sessionID it will return an error. So every sessionID can only be used once.
The issue is that when sending two request at the same time, one will fail as they both are sent with the same sessionID in the cookie.
Is there a way to make RestKit / AFNetworking only perform my calls after the last call has returned with the next sessionID?
I'd like to be able to just send the calls but have them delayed after the last call has succeeded or failed.

This is exactly what NSOperationQueue is for:
Init an NSOperationQueue and set number of concurrent tasks to 1:
NSOperationQueue *queue = [NSOperationQueue new];
queue.maxConcurrentOperationCount = 1;
Be sure to retain a reference to the queue, or it will be deallocated.
To add your HTTP calls to the queue, you'll have to subclass NSOperation. Write your subclasses so each NSOperation instance makes a synchronous HTTP request in the operation's -main method, then sets the session ID for the next operation:
-(void)main {
// read the previous session ID
NSString *sessionID = [MyDataSingleton sharedInstance].sessionID;
//make synchronous call
...
//Set the session ID for the next operation
[MyDataSingleton sharedInstance].sessionID = sessionID;

Related

Force AFNetwork POST request to be synchronous

I have a button that connects a view controller to another via a segue and when pressed, sends the text from a UITextField as a parameter to an AFNetwork POST request and must decide whether the segue can continue or not depending on whether the request was successful or not.
So I wrote this:
var proceed = false
let token = tokenTextField.text.trim()
let requestURL = "https://myapi.com/authenticate/"
// Make this a synchronous HTTP POST request so that we only decide whether
// to proceed with the segue or not once we know if the request was successful
manager.POST(requestURL,
parameters: [ "code" : token ],
success: { (operation: AFHTTPRequestOperation!, responseObject: AnyObject!) in
proceed = true
NSLog("Success! Response is \(responseObject.description)")
},
failure: { (operation: AFHTTPRequestOperation!, error: NSError!) in
println("Failure! Error is: \(error.localizedDescription)")
proceed = false
self.displayLoginAttempErrorAlert()
}).waitUntilFinished()
println("what a hard thing")
return proceed
But this code prints "what a hard thing" before anything printed within the success or error callbacks, so it is obviously asynchronous. For the reasons explained above, I want it to be synchronous. How can I force that behavior?
Please bear in mind that there is nothing wrong with the request itself. I just wanna ensure the right order of things.
UPDATE: I'm aware that forcing things to be synchronous may block the UI and that's fine for my purpose. This is like a login screen where nothing should really happen between the issuing of the request and the arrival of the response.
Thanks in advance
You can:
specify the completionQueue of the manager to be something other than the main queue (so that you don't deadlock when you block the main thread, waiting for the completion handler to be called);
dispatch_semaphore_semaphore before you start the request;
dispatch_semaphore_signal the semaphore inside the completion block of the request; and
dispatch_semaphore_wait for the semaphore after the request, but before you return from your function.
But making an asynchronous method behave synchronously is fundamentally the incorrect approach, in my opinion. Not only is it a poor UX, limits your UI, etc., but also you risk having the watchdog process kill your app.
Rather than making an asynchronous process behave synchronously in shouldPerformSegueWithIdentifier, I would instead employ a standard asynchronous pattern:
remove the existing segue you presumably have from the UIKit control to the next scene;
create a new segue between the view controllers (as shown here) that won't be performed automatically, but only programmatically (see below);
have an IBAction that performs the asynchronous request, and have a completion block that
initiates the segue programmatically, as needed.
Why dont you call your viewController in success block... we also have the same situation where user has a login screen and while authentication if failure occurs then we need to show alert else on success redirect it to homeViewController.
If you use queueCompletion block it will be same as handling it in success block if you are executing only one operation in queue.
Still needs a synchronous call then use NSURLConnection class of iOS
-[NSURLConnection sendSynchronousRequest:returningResponse:error]

Handling iOS HTTPRequest with session expire

I've developed an app that request a token to do all the HTTPRequest.
I'm handling all the 100 different requests in a class called NVEngine where I'm calling Them like:
- (void) sendRequest:(NSURLRequest *)theRequest withRequestType:(NVRequestType)requestType {
NVURLConnection *conn = [[NVURLConnection alloc] initWithRequest:theRequest
delegate:self
requestType:requestType];
if (!conn) {
NVLog(#"+++++++++++++++++++++++ UNABLE TO CREATE CONNECTION");
}
}
The problem is that the result of this request can be "session expired", and in this case I have to do a re-authentication http request to refresh token and then re-call the same previous request without an user interaction. How can i manage a callBack on my sendRequest method? from the - (void) parseResultForConnection:(NVURLConnection *)connection one? If I can return on sendRequest , I can firstly do a Synchronous request to retrieve a new token, and then retry my previous HTTPCall.
PS: I've already seen some answers on this topic like Session expire design pattern
but I didn't find what's exactly I'm looking for.
You could do it like this:
You are sending requests, I'm supposing asynchronously.
One of the request receive a session expired.
You set a static variable, for example static BOOL isRetrievingNewToken to YES. You create a static queue with blocks to call after the new token has been received, you initialize it with the request call that received session expired. If a new request is receive while isRetrievingNewToken==YES, you put it on the queue. If another request receives session expired, you put it on the queue.
When you receive the new token, you execute all blocks on the queue, remove the items, and set isRetrievingNewToken to NO.
Depending on how it looks like, it may be a good idea to implement this behavior in your connection class NVURLConnection.

How return data from an HTTP request in Swift/Objective C

I'm trying to use Coinbase's API to get information about my online bitcoin wallet, and I'm trying to use Swift's NSURLSession object to do so. Perhaps I'm missing something obvious in the Apple docs, but after reading through the information about NSURLSession and NSURLSessionTask I still do not understand how to make an HTTP request and then return the body of the response so that the body can persist throughout the life of my app. As of now I only see the ability to use completion blocks which return void, or delegates which either return void themselves or use completion blocks which also return void. I want to use the data I get from the response later in the app, but because I'm using completion blocks I must handle the response data immediately after the response arrives.
To make it clear, I want to do something along the lines of the pseudocode function below:
func makeHTTPCall(urlString : String) -> String? {
create NSURLSession object
create request with proper headers and using the passed-in urlString
use the session object to send out the request
get the response object, extract the response body as a string, and return it
}
Then later, I could call something like this:
let myObject : MyObject = MyObject()
let respData : String = myObject.makeHTTPCall("https://coinbase.com/api/v1/account/balance")
This data is returning a JSON Object string, which is the String I want to persist beyond the life of the response and its completion block. How can I do this in either Swift or Objective C, since I'll be able to use either in Xcode 6?
EDIT: Two answers have been posted, but they miss the fundamental point of this question. I need to RETURN the data which I receive from the response. Both answers (and all other answers I've seen on SO) simply print the data received. I would like code that doesn't use a void-returning completion handler, but instead returns the data so that it can be used later in the lifecycle of the app. If there is anything unclear about my question, please tell me, though I don't see how this can be made clearer.
In the edit to your question, you say:
I need to RETURN the data which I receive from the response. Both answers (and all other answers I've seen on SO) simply print the data received. I would like code that doesn't use a void-returning completion handler, but instead returns the data so that it can be used later in the lifecycle of the app. If there is anything unclear about my question, please tell me, though I don't see how this can be made clearer.
I understand the appeal of this strategy, because it feels so intuitively logical. The problem is that your networking requests should always run asynchronously (e.g. use that completion handler pattern to which you allude).
While there are techniques making a function "wait" for the asynchronous request to complete (i.e. to make the asynchronous NSURLSession method behave synchronously or use one of the old synchronous network request methods), this is a really bad idea for a number of reasons:
If you do this from the main thread, it results in a horrible user experience (the app will be unresponsive while the request is in progress and the user won't know if the app is busy doing something or whether it's frozen for some unknown reason).
Again, if you do this from the main thread, you also risk having the iOS "watch dog" process kill your app (because if you block the main queue for more than a few seconds at the wrong time, particularly as the app comes to foreground, the OS will unceremoniously terminate your app). See Technical Q&A #1693 for a discussion on the problems of doing synchronous network requests.
We generally prefer the asynchronous network techniques because they offer more features unavailable with synchronous techniques (e.g. making requests cancelable, offer progress updates when using delegate-based network requests, etc.).
You really should use the completion handler pattern that those other questions suggest, and manage the changing state of the app in those handlers. In those situations where you absolutely cannot let the user proceed until some network request is done (e.g. you can't let the user buy something until you confirm their bitcoin balance, and you can't do that until they log in), then change the UI to indicate that such a request is in progress. For example, dim the UI, disable the controls, pop up an activity indicator view (a.k.a., a "spinner"), etc. Only then would you initiate the request. And upon completion of the request, you would restore the UI. I know it seems like a lot, but it's the right way to do it when the user absolutely cannot proceed until the prior request is done.
I'd also think long and hard as to whether it's truly the case that you absolutely have to force the user to wait for the prior network request to complete. Sometimes there are situations where you can let the user do/review something else while the network request is in progress. Yes, sometimes that isn't possible, but if you can find those sorts of opportunities in your app, you'll end up with a more elegant UX.
I know that problem and use this code for synchronous requests:
func synchronousRequest() -> NSDictionary {
//creating the request
let url: NSURL! = NSURL(string: "exampledomain/...")
var request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
var error: NSError?
var response: NSURLResponse?
let urlData = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error)
error = nil
let resultDictionary: NSDictionary = NSJSONSerialization.JSONObjectWithData(urlData!, options: NSJSONReadingOptions.MutableContainers, error: &error) as! NSDictionary
return resultDictionary
}
What you are asking for is a synchronous network request. There are many ways to do this, such as...
NSData's init(contentsOfURL aURL: NSURL!)
NSURLConnection's synchronous request method
...etc.
These methods will block the current thread until they complete - which can be a potentially long time. Network requests can have very high timeouts, it may be several minutes before the device gives up. NSData's init with contents of URL will return NSData, not void, and does not execute asynchronously. It will block until it is complete, which is why it's recommended to not do these types of requests from the main thread. The UI will be frozen until it completes.
In general the use of synchronous networking methods is discouraged. Asynchronous network requests are greatly preferred for a number of reasons. Using an asynchronous method that takes a completion block as a parameter will not prevent you from using the returned data elsewhere in your application. The block is executed when the network request has finished (wether it succeeds or fails) and it is passed the data, response metadata, and error. You are free to do what you want with that data - nothing prevents you from persisting it, passing it off to another object, etc. Based on your comments it sounds like you want to take the data that was the result of the network request and set it as the value of a property somewhere - that is entirely doable using an asynchronous method that uses a block as a completion handler.
In objective-C you can use __block and get the data when the operation finishes:
__block NSData *myData;
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:urlString]
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
myData = data;
}] resume];

Switch between different API hosts

I'm working on an app which primarily works with an API that will be installed in an internal system. The API is also accessible via the public internet. The client wants to allow users to enter both an internal and external (public internet) URL that the app will then connect to depending on availability of the internal and external URLs.
The app is basically done with the exception that it currently connects to the internal URL only for all it's API calls. I'm using AFNetworking with block-based completion/failure invocations for each API call.
Based on the logic that we have designed, the app will always check for the API's availability by querying for the server's current time. This is done by calling http://internal_url/api/time. If this API fails to return an appropriate respond, we'll switch to the external URL http://external_url/api/time and call the same API on that URL. If both fails, the app will inform the user accordingly and not perform any other queries to the API.
Without revealing too much, here's some code on how I the API calls are currently setup:
- (void)someAPIMethodCall:(NSDictionary *)parameters completionBlock:block failure:block {
// query /api/time and return the URL (internal/external) that is currently up
AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:<url returned from above query>];
[client operationWithSuccess:block failure:block];
}
So my question would be: what is the best way to get the query /api/time method above to work? Obviously, this method needs to complete and return either the internal/external URL so that the subsequent actual API query could use. AFAIK, AFNetworking calls are block-based so it will return before the above /api/time returns. I've also thought of a separate class that uses NSURLConnection synchronously which will block the main-thread while it waits for the /api/time to return.
I'd like to tell you to simply use the same URL internally and externally (via DNS) but that's not what you want.
I think you're asking how to conditionally call the other url.
You want someAPIMethodCall to be asynchronous... so you don't want to block on the call to checking for the correct api to call.
Aside from caching the results so you don't have to do this every time, you simply want to call another block based method of your own that has a completion block which passes IN a parameter of the URL to call for your real query.
- (void)someAPIMethodCall:(NSDictionary *)parameters completionBlock:(void (^)(void))succesBlock failure((^)(void)):failureBlock {
[self callBlockWithMyApiUrl:^(NSString *apiUrl){
AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:apiUrl];
[client operationWithSuccess:successBlock failure:failureBlock];
} onFailure:^{
failureBlock
}
}
- (NSString *)callBlockWithMyApiUrl:(NSString * (^)(void))success (void (^)(void))failure
{
// Your code to test for the working URI
// If you're doing it this way, I'd suggest caching the result.
// Subscribe to networking interface changes to dump the cache.
}

Can an ASIHTTPRequest be retried?

Is there a way to retry an ASIHTTPRequest? I'm using custom authentication in a REST service instead of basic HTTP authentication and I would like to retry a request if the session has expired. Calling startAsynchronous a second time on a request causes an exception. I would like to do something like this:
[request setCompletionBlock:^{
if ([request responseStatusCode] == 500)
{
// try to login again in case token expired
[server loginAndRetryRequest:request];
} else {
// parse response
}
}];
loginAndRetryRequest: will do another ASIHTTPRequest to login and when it is complete it will start the original request again from it's CompletionBlock (assuming this is possible somehow)?
It should be possible to make a copy of the request and then execute -startAsynchronous again on the copy.
Support for NSCopying protocol was added in release 1.5, which also includes automatic retry in case of timeout (selector -setNumberOfTimesToRetryOnTimeout:.
Another option could be checking their source code to see how the automatic retry is done in case of timeout, but copying and re-issuing the request should work (that was the reason to add support for NSCopying in the first place).

Resources