I am new to RESTKit, and I want to set a timeout interval for some of my REST requests. This question refers to the same subject. But the answers there are not what I need.
I want to set a different timeout for different requests, and that's why I don't want to subclass RKObjectManager. I am using getObjectsAtPath:parameters:success:failure and postObject:path:parameters:success:
Is there a way to set each of those requests' timeout separately?
Not when using getObjectsAtPath:parameters:success:failure or postObject:path:parameters:success:.
You will need to call requestWithObject:method:path:parameters: instead, then edit the request to set your timeout, then call objectRequestOperationWithRequest:success:failure: to get the operation to run, then call enqueueObjectRequestOperation: to have it executed.
Related
I have the following code below that loops through an array. I need to check if the finish or fail selector has been called iterating to the next object in my dataArray.
for (id object in dataArray) {
[client setDidFinishSelector:#selector(getDataFinish:)];
[client setDidFailSelector:#selector(getDataFail:)];
[client getData:object];
}
In my getDataFinish method I assign values and I am trying to keep it in order. If I use the above method, the values can get out of order since the client response time can be different for each request..
I see two possible solutions, depending on what you're actually trying to do. It sounds like you're making calls to the internet, so yes you will get varied response time (or no response at all). Because of this, I would recommend using NSNotification. See this answer for more information about that.
Another option is making a flag in your code (AKA a BOOL) that you set to YES when your method has completed. Again, if you're making calls to the web I would not recommend this method as you are setting yourself up for an infinite loop if the user has no service and the BOOL never changes.
If you are still having trouble let me know and I can provide a more detailed answer.
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.
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
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?
While using a 3rd party API, I have the requirement to cancel all traffic when a custom response header is set to a certain value. I am trying to find a nice place to do this check only once in my code (and not in every success/failure block, where it works fine). From what I understand, this could be done by overriding -(void)enqueueHTTPRequestOperation:(AFHTTPRequestOperation *)operation in my custom AFHTTPClient subclass, but when I implement it like that:
-(void)enqueueHTTPRequestOperation:(AFHTTPRequestOperation *)operation
{
NSLog(#"[REQUEST URL]\n%#\n", [operation.request.URL description]);
NSLog(#"[RESPONSE HEADERS]\n%#\n", [[operation.response allHeaderFields] descriptionInStringsFileFormat]);
[super enqueueHTTPRequestOperation:operation];
}
the response headers are nil. Can anybody help me with that?
At the moment when operations are being created and enqueued in AFHTTPClient, they will not have the response from the server--that will be assigned when the request operation is actually executed.
Although the requirement to cancel all traffic seems unorthodox (at least if outside of the conventions of HTTP), this is easy to accomplish:
In your AFHTTPClient subclass, add a BOOL property that stores if requests should be prevented, and then used in enqueueHTTPRequestOperation. Then, override HTTPRequestOperationWithRequest:success:failure: to execute the specified success block along with some logic to set the aforementioned property if the salient response is present.