AFNetworking 3.0 AFHTTPSessionManager using NSOperation
I've written a quick little set of classes (https://github.com/robertmryan/AFHTTPSessionOperation/) that wrap AFHTTPSessionManager
requests in asynchronous NSOperation
subclass. You can then use that to enjoy maxConcurrentOperation
constraints, or operation dependencies.
For example, here's an example where we issue two concurrent requests and have a completion operation dependent upon completion of both of those requests:
// ViewController.m
#import "ViewController.h"
#import "AFNetworking.h"
#import "AFHTTPSessionOperation.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString *urlString1 = @"...";
NSString *urlString2 = @"...";
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"AFHTTPSessionManager queue";
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"All done");
}];
NSOperation *op1 = [AFHTTPSessionOperation operationWithManager:manager HTTPMethod:@"GET" URLString:urlString1 parameters:nil uploadProgress:nil downloadProgress:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(@"finished 1");
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"failed 1 - error = %@", error.localizedDescription);
}];
[completionOperation addDependency:op1];
NSOperation *op2 = [AFHTTPSessionOperation operationWithManager:manager HTTPMethod:@"GET" URLString:urlString2 parameters:nil uploadProgress:nil downloadProgress:nil success:^(NSURLSessionDataTask *task, id responseObject) {
NSLog(@"finished 2");
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSLog(@"failed 2 - error = %@", error.localizedDescription);
}];
[completionOperation addDependency:op2];
[queue addOperations:@[op1, op2] waitUntilFinished:false];
[[NSOperationQueue mainQueue] addOperation:completionOperation]; // do this on whatever queue you want, but often you're updating UI or model objects, in which case you'd use the main queue
}
@end
It's worth noting that since you're only dealing with two requests, you could also use dispatch groups to accomplish the same thing:
// ViewController.m
#import "ViewController.h"
#import "AFNetworking.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString *urlString1 = @"...";
NSString *urlString2 = @"...";
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[manager GET:urlString1 parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"finished 1");
dispatch_group_leave(group);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"failed 1 - error = %@", error.localizedDescription);
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[manager GET:urlString2 parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"finished 2");
dispatch_group_leave(group);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"failed 2 - error = %@", error.localizedDescription);
dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"All done");
});
}
@end
With dispatch groups, you just need to be careful that every path within both the success
and failure
blocks call dispatch_group_leave
.
Flipper
Updated on June 30, 2022Comments
-
Flipper almost 2 years
I'm stuck now some time and I need help. So in AFNetworking 2.0 we have
AFHTTPRequestOperation
so I could easily useNSOperationQueue
and have some dependencies. So what we have now is onlyAFHTTPSessionManager
andNSURLSession
that does not subclassNSOperation
. I have classAPIClient
that subclassesAFHTTPSessionManager
. I am using that class as singleton assharedClient
. I have overriden GET and POST so for example GET looks this:- (NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(NSDictionary *)parameters success:(void (^)(NSURLSessionDataTask *task, id responseObject))success failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure { NSURLSessionDataTask *task = [super GET:URLString parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) { success(task, responseObject); } failure:^(NSURLSessionDataTask *task, NSError *error) { failure(task, [Response createErrorWithAFNetworkingError:error]); }]; return task; }
Do you have any idea how to implement in that manner (if it's possible) to wrap that as
NSOperation
? So what I want to do - I want to be able to run in parallel two network calls, and after that have another method call that depends on second network call of first two calls. Do you have any idea what would be best approach?-
Flipper over 8 yearsYes, please, if you don't mind
-
Flipper over 8 yearsThank you very much. Yes, oh sorry, I was not clear enough when I read it again - dependent upon both requests :)
-
Max over 8 yearsif you want to get exact error on server side than you can check in this answer 'stackoverflow.com/a/35723726/3463712'
-
-
Flipper over 8 yearsThat is exactly what I was looking for, great :)
-
Rob over 8 years@Flipper - BTW, I've updated answer with GCD alternative if you don't want to introduce those external classes.
-
grayson about 8 years@Rob - You state: "as you are only dealing with 2 requests....." at one point do you suggest that using dispatch groups would not be appropriate in this use case? Thx
-
Rob about 8 years@grayson - It's a matter of opinion, but I really prefer
NSOperation
-based solutions for networking, as it handles this dependency stuff really well, allows you to control the degree of concurrency without risking timeout issues, is really graceful in handling cancellations, etc. For simple situations, dispatch groups are fine, but I often lean towardsNSOperation
in most cases. -
grayson about 8 years@Rob, thanks for the feedback. I apolgise for asking and this may require a full question in itself, but when you say it "is really graceful in handling cancellations" could you explain that. I am currently calling 7 endpoints (same api, just different data) using AFHTTPSessionManager (as a singleton), calling each subsequent one in the returning block because I don't want to call the next endpoint if the first one fails. Can I do this better with NSOperation (using AFNetworking 2.x)?
-
Rob about 8 yearsFirst, I would never go back to 2.x. I'd just wrap v3 tasks in
NSOperation
subclass if I needed operations. Second, if you're really going to run these sequentially (and suffer the performance penalty that entails), then neither operations nor dispatch groups are needed: Just initiate each upon the completion of the prior one. -
Ketan Parmar about 8 years@Rob : you have suggests in last comment
Just initiate each upon the completion of the prior one
, By that do you mean something likerecursion
? Like calling next request from completion block of previous ? -
Rob about 8 years@Lion Yeah, it's type of recursion (though because stuff is happening asynchronously, some of the uglier issues of cramming a lot of stuff onto a stack are not relevant here). But note, only do this type of sequential requests if you absolutely have to (i.e. the information necessary to build one request required information supplied by the prior request) because you pay huge penalty performance for sequential requests. It's much better to design solution that allows you to issue requests concurrently.
-
JIANG over 7 yearsRob, I'm getting this error. ---------------------------------------------------------Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[AFHTTPSessionManager dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:]: unrecognized selector sent to instance 0x7b497620'
-
Rob over 7 years@Aviva - Are you using the latest version of AFNetworking or an older version? Earlier versions didn't have those progress parameters. If you're using the latest version of AFNetworking, it should work fine. I just tested the above code with the latest version of AFNetworking without incident.
-
JIANG over 7 yearsThank you Rob! I upgraded it to AFNetworking 3.1, now i'm getting a cancelled error. Please help!
-
JIANG over 7 yearsAFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] init]; NSOperation *operation = [AFHTTPSessionOperation operationWithManager:manager HTTPMethod:@"POST" URLString:urlString parameters:parameters uploadProgress:nil downloadProgress: nil success:^(NSURLSessionDataTask *task, id responseObject) { NSLog(@"Response --->>>: %@", responseObject ); } failure:^(NSURLSessionDataTask *task, NSError *error) { NSLog(@"Error --->>>: %@", error.localizedDescription); }];
-
Rob over 7 yearsCan you share the full text of the error? And don't just look at
error.localizedDescription
, but let's see the wholeNSError
. Let's make sure the error isn't something unrelated (like, perhaps trying to connect to http resource (versus https) without appropriate Info.plist settings to allow unsecured network connections). -
JIANG over 7 yearsThank you Rob! I was able to track down to ATS related error. The error code is 999. My web server I was calling is TLS 1.2 only.
-
JIANG over 7 years@Rob, can you help me with this time out problem? I'm using your class and not sure what I did wrong. stackoverflow.com/questions/42821463/…
-
JIANG about 7 years@Rob, you fixed my last problem, however I run into something new, my session are not longer there. I'm a little lost and not sure how to do that. stackoverflow.com/questions/43149842/…
-
iPhone over 4 years@Rob: How can I implement the same scenario with SOAP request.
-
Rob over 4 years@iPhone - It seems like it would be basically the same sort of approach except you’ve got all the overhead of composing well-formed SOAP requests, parsing the XML responses, etc., just adding another layer on top of this. If I were starting a project nowadays, though, I’m not sure I’d pick AFNetworking/Objective-C; I would lean towards Alamofire/Swift.