How to batch request with AFNetworking 2?
Solution 1
Thanks Sendoa for the link to the GitHub issue where Mattt explains why this functionality is not working anymore. There is a clear reason why this isn't possible with the new NSURLSession
structure; Tasks just aren't operations, so the old way of using dependencies or batches of operations won't work.
I've created this solution using a dispatch_group
that makes it possible to batch requests using NSURLSession
, here is the (pseudo-)code:
// Create a dispatch group
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i < 10; i++) {
// Enter the group for each request we create
dispatch_group_enter(group);
// Fire the request
[self GET:@"endpoint.json"
parameters:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
// Leave the group as soon as the request succeeded
dispatch_group_leave(group);
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
// Leave the group as soon as the request failed
dispatch_group_leave(group);
}];
}
// Here we wait for all the requests to finish
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// Do whatever you need to do when all requests are finished
});
I want to look write something that makes this easier to do and discuss with Matt if this is something (when implemented nicely) that could be merged into AFNetworking. In my opinion it would be great to do something like this with the library itself. But I have to check when I have some spare time for that.
Solution 2
Just updating the thread... I had the same problem and after some researches I found some good solutions, but I decided to stick with this one:
I am using the project called Bolts. So, for the same sample above posted by @Mac_Cain13, it would be:
[[BFTask taskWithResult:nil] continueWithBlock:^id(BFTask *task) {
BFTask *task = [BFTask taskWithResult:nil];
for (int i = 0; i < 10; i++) {
task = [task continueWithBlock:^id(BFTask *task) {
return [self executeEndPointAsync];
}];
}
return task;
}] continueWithBlock:^id(BFTask *task) {
// Everything was executed.
return nil;
}];;
- (BFTask *) executeEndPointAsync {
BFTaskCompletionSource *task = [BFTaskCompletionSource taskCompletionSource];
[self GET:@"endpoint.json" parameters:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
[task setResult:responseObject];
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
[task setError:error];
}];
}];
return task.task;
}
Basically, it's stacking all of the tasks, waiting and unwrapping until there is no more tasks, and after everything is completed the last completion block is executed.
Another project that does the same thing is RXPromise, but for me the code in Bolts was more clear.
Solution 3
For request
which can be post
or get
, you can use AFNetworking 2.0
for batch operation as firstly you need to create operation like this:
//Request 1
NSString *strURL = [NSString stringWithFormat:@"your url here"];
NSLog(@"scheduleurl : %@",strURL);
NSDictionary *dictParameters = your parameters here
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] requestWithMethod:@"POST" URLString:strURL parameters:dictParameters error: nil];
AFHTTPRequestOperation *operationOne = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operationOne = [AFHTTPResponseSerializer serializer];
[operationOne setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
//do something on completion
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(@"%@",[error description]);
}];
//Request 2
NSString *strURL1 = [NSString stringWithFormat:@"your url here"];
NSLog(@"scheduleurl : %@",strURL);
NSDictionary *dictParameters1 = your parameters here
NSMutableURLRequest *request1 = [[AFHTTPRequestSerializer serializer] requestWithMethod:@"POST" URLString:strURL1 parameters:dictParameters1 error: nil];
AFHTTPRequestOperation *operationTwo = [[AFHTTPRequestOperation alloc] initWithRequest:request1];
operationTwo = [AFHTTPResponseSerializer serializer];
[operationTwo setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
//do something on completion
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(@"%@",[error description]);
}];
//Request more here if any
Now perform batch operation like this :
//Batch operation
//Add all operation here
NSArray *operations = [AFURLConnectionOperation batchOfRequestOperations:@[operationOne,operationTwo] progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations)
{
NSLog(@"%i of %i complete",numberOfFinishedOperations,totalNumberOfOperations);
//set progress here
yourProgressView.progress = (float)numberOfFinishedOperations/(float)totalNumberOfOperations;
} completionBlock:^(NSArray *operations)
{
NSLog(@"All operations in batch complete");
}];
[[NSOperationQueue mainQueue] addOperations:operations waitUntilFinished:NO];
Solution 4
Currently, NSURLSession
tasks are not suitable for the same kind of patterns request operations use. See the answer from Mattt Thompson here regarding this issue.
Direct answer: if you need dependencies or batches, you'll still need to use request operations.
Solution 5
On AFNetworking 2.0, AFHTTPClient
has been split on AFHTTPRequestOperationManager
and AFHTTPSessionManager
, so probably you could start with the first, which has operationQueue
property.
Mac_Cain13
I love to code, solve hard problems and create good looking fast applications. Most of the time I develop iOS app and after a long time of Objective-C development I now love to code in Swift. Android and web development also have their fair share of my development time and I like to work on small side-projects like browser extensions or handy shell scripts. In my vision it is important to give the user an unforgettable experience. Great performance, amazing interaction design and an intuitive user interface is just as important as a rock solid codebase.
Updated on June 10, 2022Comments
-
Mac_Cain13 about 2 years
So I'm rewriting an app for iOS 7 with AFNetworking 2.0 and I'm running into the issue of sending a batch of requests at once and tracking their progress. In the old AFNetworking there was the
enqueueBatchOfHTTPRequestOperations:progressBlock:completionBlock:
method onAFHTTPClient
, this is clearly refactored out and I'm a bit confused on how to enqueue multiple requests.I have created a subclass of
AFHTTPSessionManager
and I'm using thePOST:...
andGET:...
methods to communicate with the server. But I can't find anything in the code and/or docs to enqueue multiple requests at once like with the oldAFHTTPClient
.The only thing I can find is the undocumented
batchOfRequestOperations:progressBlock:completionBlock:
method onAFURLConnectionOperation
, but that looks like the iOS 6 way of doing this.Clearly I'm missing something in the new
NSURLSession
concept that I should use to batch requests or looking over a new AFNetworking feature. Hope someone can help me on the right track here!tl;dr: How can I send a batch of requests with my
AFHTTPSessionManager
subclass? -
Chrizzor over 10 yearsWhat if the first iteration leaves the group before the second iteration enters it?
-
Mac_Cain13 over 10 yearsThat's not a problem. In that case the for-loop is still blocking the thread. So it will append all other requests to the group, only after the for-loop completes
dispatch_group_notify()
will be called and once all "iterations" leave the group the block is called. -
zengr over 10 yearsI am a little new to objc/afnetworking stuff. What is the
self
object here? Its notAFHTTPRequestOperationManager
. -
ninjaneer over 10 yearsUnfamiliar with GCD, but do you have to release the group after you're done?
-
Mac_Cain13 over 10 yearsIf I'm right ARC will handle the release/retain of the group.
-
Ríomhaire almost 10 yearsHow would you go about making the requests concurrent? i.e. using (developer.apple.com/library/ios/documentation/General/Conceptal/… - A concurrent dispatch queue
-
tothemario almost 10 yearsDoesn't it have to
dispatch_release
insidedispatch_group_notify
to release the empty group?