Serially queueing conditional HTTP requests with AFNetworking and NSOperationQueue - ios

I am refactoring some existing code. It has about 20 HTTP requests which are executed serially, assuming the previous one returns HTTP status 200 (OK). If the status is not 200, the process stops. The current code is a mess of delegate callbacks, instance variables, a giant switch statement, and separate methods for each call.
I'd like to use AFNetworking instead. I figure that I will need to make a series of AFJSONRequestOperation objects and put them into an NSOperationQueue with maxConcurrentOperationCount set to 1 so it runs serially.
The default behavior of an NSOperationQueue is to continue through the operations whether or not the previous operation was successful. However I would like the queue to cancel all operations if one of the operations calls its failure block (for example, if the HTTP request returns 404 (file not found)).
Since the behavior of AFJSONRequestOperation is not very configurable, do I need to subclass it to achieve what I want? Is there another built-in feature of AFNetworking that will allow me to do this simply?

Related

AFNetworking request operations - creating a Funnel/Bottle-neck queue singleton

Setup
I have two areas in my program - branch 1 and branch 2 - where network requests are made asynchronously 1 concurrent GET request at a time for each area. Requests are sent 1 at a time because the server has a tiny few milliseconds of a grace period for any user making requests to the server. Running 1 concurrent request at a time aims to aid that grace period.
The operation has been made such that if any request fails in any branch - there is a fail safe to just repeat the request again.
Problem:
When I run these branches separately from each other, i.e. not at the same time, the server is happy. However, as soon as I allow for both operations to take place at the same time the server throws a 429 error which is the error letting the user know that there are too many requests coming in at any one time. What then happens is that half of the requests fails, and then because of the fail safe the requests are being sent again for processing until they're all completed.
It's a waste of time and resources on behalf of the user's data package.
Scenario
Branch 1 sends out 10 requests (normally its within the thousands, processing one at a time, but for simplicity purposes we'll keep it at 10).
These 10 requests will be processed concurrently 1 at a time very quickly and return with their appropriate data objects.
Now if we run branch 1 operation again - 10 requests concurrently and then add branch 2's 10 requests, this will create 20 request operations - twice as many - GOING OUT to the server which will cause roughly half of the requests from both branch 1 and 2 to fail, with the fail-safe then kicking in to resend those request operations. boohoo.
Question
Is there a way to funnel these request operations coming out of my application regardless of where these two branches lay within my app so that we can control and bottleneck the request operations going out to the server so that the server won't be bombarded with requests only for half of the requests to fail?
Possible solution
One idea I had was to create a singleton that would act as a queue as well as the bottle neck required that would allow me to add request operations from ANY branch anywhere within my code base - hence why i thought a singleton would be a great idea for this - and then as soon as it realises a request operation has been added to the queue to then to have that queue run 1 concurrent request at at time.
This I think will work great for my purposes, I just didn't know how to handle the completion blocks of the requests since the request operation from Branch 1 are different from the completion blocks of the request operations from Branch 2.
How would you manage that? Is there anything we can do?
Edit 1 - What I'm currently doing
In Branch 1 - Using AFHTTPOperation:
1) For each process I create a for loop that iterates 10 times adding 10 AFHTTPRequestOperations to a local array called multipleOperations of type NSMutableArray.
2) I then add the multipleOperations array to a batchOfRequestOperations method like so:
NSArray *operations = [AFURLConnectionOperation batchOfRequestOperations:muiltipleOperations
progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations){
//Do some stuff here for each iteration
}
completionBlock:^(NSArray*operations){
//Call the method that repeats this process again.
}];
3) I then add the operations to the mainQueue like so
[[NSOperationQueue mainQueue] addOperations:operations waitUntilFinished:NO];
I'm now wondering whether this process in branch one even has it set to 1 concurrenncy, cant see that it is :\
In Branch 2 using NSURLSessionDataTask (just realised, wouldnt it be easier if they were all using the same subclass? lol
I notice that I am using a AFHTTPSessionManager that creates NSURLSessionDataTasks.. which doesnt run in a for loop but instead calls its own method for another request to be sent out EVERY TIME the request executes its own completion block. So it will forever keep calling itself as soon as the previous request in branch 2 is done. This is how I know its running only 1 request at a time concurrently. So thats good.
Is there anyway to make this more cleaner?
No where am I using HTTPRequestOperationManager :( Can someone show me how things can be done properly?
Thank you.
Based on your edit, I can see a some things you'll probably want to change. First off, yes, it will be much easier if you use either AFHTTPOperations or NSURLSessionDataTasks, but not both.
If you're going to use AFHTTPOperation, then don't queue the operations on [NSOperationQueue mainQueue]. The reason being is that that is the main UI queue and you don't want your network operations blocking it. What you should be doing is creating your own NSOperationQueue and setting its concurrency to 1.
NSOperationQueue *networkQueue = [[NSOperationQueue alloc] init];
networkQueue.maxConcurrentOperationCount = 1;
Using your idea of a singleton to store that queue somewhere you can access from anywhere in your app would be a good way to go about making sure you send all your network operations through the same queue.
That being said, I'd recommend using NSURLSessionDataTask with AFHTTPSessionManager instead. The docs for AFNetworking state:
Developers targeting iOS 7 or Mac OS X 10.9 or later that deal
extensively with a web service are encouraged to subclass
AFHTTPSessionManager, providing a class method that returns a shared
singleton object on which authentication and other configuration can
be shared across the application.
That sounds like a good way to accomplish what you want. Basically you'll create a subclass of AFHTTPSessionManager with a class method that creates an instance initialized with a custom NSURLSessionConfiguration. You can configure the NSURLSessionConfiguration however is appropriate for your app, but the main property you'll be interested in for this particular issue is HTTPMaximumConnectionsPerHost:
This property determines the maximum number of simultaneous
connections made to each host by tasks within sessions based on this
configuration.
With that set to 1, you can let AFHTTPSessionManager (well, actually, NSURLSession) worry about making sure that only one request is made to your server at any given time.
Something along these lines:
#interface AppHTTPSessionManager : AFHTTPSessionManager
+ (instancetype)appSession;
#end
#implementation AppHTTPSessionManager
+ (instancetype)appSession {
static AppHTTPSessionManager *_appSession = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.HTTPMaximumConnectionsPerHost = 1;
_appSession = [[AppHTTPSessionManager alloc] initWithSessionConfiguration:configuration];
});
return _appSession;
}
#end
Then, whenever you want to make a network call, make sure you create your NSURLSessionDataTask from [AppHTTPSessionManager appSession]. If you do, those tasks should automatically be limited to one request to the server at a time.

objective-c, possible to queue async NSURLRequests?

I realize this question sounds contradictory. I have several Async requests going out in an application. The situation is that the first async request is an authentication request, and the rest will use an access token returned by the successful authentication request.
The two obvious solutions would be:
run them all synchronous, and risk UI block. (bad choice)
run them async, and put request 2-N in the completion handler for the first one. (not practical)
The trouble is that the subsequent requests may be handled anywhere in the project, at anytime. The failure case would be if the 2nd request was called immediately after the 1st authentication request was issued, and before the access token was returned.
My question thus is, is there any way to queue up Async requests, or somehow say not to issue them until the first request returns successfully?
EDIT:
Why (2) is not practical: The first is an authentication request, happening when the app loads. The 2nd+ may occur right away, in which case it is practical, but it also may occur in a completely separate class or any other part of a large application. I can't essentially put the entire application in the completion handler. Other accesses to the API requests may occur in other classes, and at anytime. Even 1-2 days away after many other things have occurred.
SOLUTION:
//pseudo code using semaphore lock on authentication call to block all other calls until it is received
// at start of auth
_semaphore = dispatch_semaphore_create(0)
// at start of api calls
if(_accessToken == nil && ![_apiCall isEqualToString:#"auth]){
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
}
// at end of auth with auth token
dispatch_semaphore_signal([[SFApi Instance] semaphore]);
_accessToken = ...;
This sounds like a case where you'd want to use NSOperation's dependencies
From apple docs:
Operation Dependencies
Dependencies are a convenient way to execute operations in a specific order. You can add and remove dependencies for an operation using the addDependency: and removeDependency: methods. By default, an operation object that has dependencies is not considered ready until all of its dependent operation objects have finished executing. Once the last dependent operation finishes, however, the operation object becomes ready and able to execute.
note that in order for this to work, you must subclass NSOperation "properly" with respect to KVO-compliance
The NSOperation class is key-value coding (KVC) and key-value observing (KVO) compliant for several of its properties. As needed, you can observe these properties to control other parts of your application.
You can't really have it both ways-- there's no built-in serialization for the NSURLConnection stuff. However, you are probably already funneling all of your API requests through some common class anyway (presumably you're not making raw network calls willy-nilly all over the app).
You'll need to build the infrastructure inside that class that prevents the execution of the later requests until the first request has completed. This suggests some sort of serial dispatch queue that all requests (including the initial auth step) are funneled through. You could do this via dependent NSOperations, as is suggested elsewhere, but it doesn't need to be that explicit. Wrapping the requests in a common set of entry points will allow you to do this any way you want behind the scenes.
In cases like this I always find it easiest to write the code synchronously and get it running on the UI thread first, correctly, just for debugging. Then, move the operations to separate threads and make sure you handle concurrency.
In this case the perfect mechanism for concurrency is a semaphore; the authentication operation clears the semaphore when it is done, and all the other operations are blocking on it. Once authentication is done, floodgates are open.
The relevant functions are dispatch_semaphore_create() and dispatch_semaphore_wait() from the Grand Central Dispatch documentation: https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html#//apple_ref/doc/uid/TP40008079-CH2-SW2
Another excellent solution is to create a queue with a barrier:
A dispatch barrier allows you to create a synchronization point within a concurrent dispatch queue. When it encounters a barrier, a concurrent queue delays the execution of the barrier block (or any further blocks) until all blocks submitted before the barrier finish executing. At that point, the barrier block executes by itself. Upon completion, the queue resumes its normal execution behavior.
Looks like you got it running with a semaphore, nicely done!
Use blocks... 2 ways that I do it:
First, a block inside of a block...
[myCommKit getPlayerInfoWithCallback:^(ReturnCode returnCode, NSDictionary *playerInfo) {
if (playerInfo) {
// this won't run until the first one has finished
[myCommKit adjustSomething: thingToAdjust withCallback:^(ReturnCode returnCode, NSDictionary *successCode) {
if (successCode) {
// this won't run until both the first and then the second one finished
}
}];
}
}];
// don't be confused.. anything down here will run instantly!!!!
Second way is a method inside of a block
[myCommKit getPlayerInfoWithCallback:^(ReturnCode returnCode, NSDictionary *playerInfo) {
if (playerInfo) {
[self doNextThingAlsoUsingBlocks];
}
}];
Either way, any time I do async communication with my server I use blocks. You have to think differently when writing code that communicates with a server. You have to force things to go in the order you want and wait for the return success/fail before doing the next thing. And getting used to blocks is the right way to think about it. It could be 15 seconds between when you start the block and when it gets to the callback and executes the code inside. It could never come back if they're not online or there's a server outage.
Bonus way.. I've also sometimes done things using stages:
switch (serverCommunicationStage) {
case FIRST_STAGE:
{
serverCommunicationStage = FIRST_STAGE_WAITING;
// either have a block in here or call a method that has a block
[ block {
// in call back of this async call
serverCommunicationStage = SECOND_STAGE;
}];
break;
}
case FIRST_STAGE_WAITING:
{
// this just waits for the first step to complete
break;
}
case SECOND_STAGE:
{
// either have a block in here or call a method that has a block
break;
}
}
Then in your draw loop or somewhere keep calling this method. Or set up a timer to call it every 2 seconds or whatever makes sense for your application. Just make sure to manage the stages properly. You don't want to accidentally keep calling the request over and over. So make sure to set the stage to waiting before you enter the block for the server call.
I know this might seem like an older school method. But it works fine.

Can AFNetworking handle a queue of requests?

My example on iOS 6:
10 Multi-Part requests need to be sent (in order) to the server.
(so the request forms a queue)
progress should be shown.
if one request fails all following should fail
a request queue should be cancellable
Can AFNetworking help me with this? Or should I try to build something with NSOperations and run the loops myself?
If I need to pass context data between theses requests for example a transaction id produced by the first request. Are there any considerations about thread visibility I need to consider?
AFNetworking can do this. I recommend that you use AFHTTPRequestOperationManager (which itself uses NSOperation), rather than AFHTTPSessionManager. There are ways to do it with AFHTTPSessionManager, but none as elegant as with operations.
Under the hood, here's what you'd do without the manager:
You will use a request serializer to make your NSMutableURLRequest (for example, [AFHTTPRequestSerializer -multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error:]; there's a similar JSON request serializer too).
Once you have a URL Request, make the operation with [AFHTTPRequestOperation -initWithRequest:]. You should also set its completion blocks.
Finally, add your operation to [AFHTTPRequestOperationManager manager].operationQueue and start it.
Now that you understand how this is basically all working together, here's a simpler approach:
Subclass AFHTTPRequestOperationManager, optionally setting the requestSerializer if you don't like the default
Override (or copy with new implementation) -POST:parameters:constructingBodyWithBlock:success:failure:] - what you want to do is NOT start your operation right away.
Set the NSOperation dependency chains
start the first one

NSURLConnection connectionDidFinishLoading Not Called Randomly

I have an iPad application where I am making asynchronous calls using NSURLConnection. In some cases I receive all the response data in connection:didReceiveData:, but connectionDidFinishLoading is never called. There is no error. This is somewhat random, because the same responses do complete at other times.
The way that my class works is that around 20 requests are sent in a row using:
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
And then I just wait for them to come back. Is this a valid way to create multiple requests?
Here is a sample header of a response that did not complete. It is indistinguishable from the header of a response that did complete.
>Keep-Alive: timeout=5, max=100
>Transfer-Encoding: Identity
>Server: Apache/2.2.9 (Unix) mod_ssl/2.2.9 OpenSSL/0.9.8b mod_jk/1.2.26
>Content-Type: application/json;charset=UTF-8
>Connection: Keep-Alive
>Date: Wed, 03 Apr 2013 05:25:32 GMT
>Cache-Control: private, no-transform, max-age=600
One weird symptom of the problem is that I started checking the expected content length using:
long long download_size =[response expectedContentLength];
The Content-Length is never set in the http header. When a request fails the download_size is -1 (expected), when the same request does not fail the download_size is set to some number. However, there are many cases where the download_size is not set and the response does not fail.
This is not a great way to initiate 20 requests because:
The requests operate concurrently, and thus if you don't have some class that encapsulates all of the response data and the like from the individual requests, your app can be confusing the response data from various requests;
Because they happen concurrently and because iOS only allows five concurrent requests to a given server, it will delay (and possibly timeout) the others.
You have a bunch of different approaches, but you probably:
You might want to be doing this network stuff on a background queue;
If you want concurrent operations (and there's an observable performance benefit from doing so), you could use NSOperationQueue to have concurrent operations, but limit how many concurrent operations are ongoing (to 4 or 5) with the use of maxConcurrentOperationCount. This process is trivial if you're using synchronous network operations in these background operations, but surprisingly complicated if you use asynchronous network operations with your own delegate methods in the background queue, though.
If you really need (a) to use the asynchronous network calls using your own delegate methods (e.g. updating progress views, streaming protocols, etc.); and (b) you want to enjoy concurrent operations, it will be considerably easier to use AFNetworking than it will be to write your own. I went through this exercise of writing my own, but having done that exercise once, I now more fully appreciate what AFNetworking brings to the table.
Sure, you could get around all of this by managing your own array of pending network requests, initiate the first one, and have the connectionDidFinishLoading and/or didFailWithError kick off the next one in your queue, but you lose the performance gain of concurrency. But it is one simple work-around.
I do hope you are use separate objects here. You are saying
NSURLConnection *connection =
[[NSURLConnection alloc] initWithRequest:request
delegate:self startImmediately:YES];
Well, I sure hope those are 20 different self objects. You must not expect this to work with a single object acting as the delegate for all 20 requests simultaneously!

AFNetworking - batched operations completion block firing before all JSON requests completed

I'm just starting using AFNetworking and have come across an anomaly when using enqueueBatchOfHTTPRequestOperations to batch together 3 AFJSONRequestOperation objects. I've found that occasionally the completionBlock for the queue fires before all of the AFJSONRequestOperation completion blocks have fired. After doing some research it seems this is to do with the AFJSONRequestOperation parsing the response data to JSON which it does in a separate queue meaning the request reports to the queue as finished before the parsing has completed.
My question therefore is - can I instead use AFHTTPRequestOperation objects for my 3 requests, and parse the JSON manually myself within the request completion block? Will this then guarantee that the queue completionBlock will only fire once all requests have completed?
I changed my code to use AFHTTPRequestOperation instead of AFJSONRequestOperation and parse the JSON manually, and I've been testing the new version for the last couple of days. I've not seen the problem occur once so I think it's safe to say the queue completion block firing before all requests have completed only happens when using AFJSONRequestOperation.

Resources