AFNetworking 3.0 AFHTTPSessionManager using NSOperation

12,001

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.

Share:
12,001
Flipper
Author by

Flipper

Updated on June 30, 2022

Comments

  • Flipper
    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 use NSOperationQueue and have some dependencies. So what we have now is only AFHTTPSessionManagerand NSURLSession that does not subclass NSOperation. I have class APIClient that subclasses AFHTTPSessionManager. I am using that class as singleton as sharedClient. 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
      Flipper over 8 years
      Yes, please, if you don't mind
    • Flipper
      Flipper over 8 years
      Thank you very much. Yes, oh sorry, I was not clear enough when I read it again - dependent upon both requests :)
    • Max
      Max over 8 years
      if you want to get exact error on server side than you can check in this answer 'stackoverflow.com/a/35723726/3463712'
  • Flipper
    Flipper over 8 years
    That is exactly what I was looking for, great :)
  • Rob
    Rob over 8 years
    @Flipper - BTW, I've updated answer with GCD alternative if you don't want to introduce those external classes.
  • grayson
    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
    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 towards NSOperation in most cases.
  • grayson
    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
    Rob about 8 years
    First, 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
    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 like recursion? Like calling next request from completion block of previous ?
  • Rob
    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
    JIANG over 7 years
    Rob, I'm getting this error. ---------------------------------------------------------Ter‌​minating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[AFHTTPSessionManager dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:d‌​ownloadProgress:succ‌​ess:failure:]: unrecognized selector sent to instance 0x7b497620'
  • Rob
    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
    JIANG over 7 years
    Thank you Rob! I upgraded it to AFNetworking 3.1, now i'm getting a cancelled error. Please help!
  • JIANG
    JIANG over 7 years
    AFHTTPSessionManager *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
    Rob over 7 years
    Can you share the full text of the error? And don't just look at error.localizedDescription, but let's see the whole NSError. 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
    JIANG over 7 years
    Thank 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
    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
    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
    iPhone over 4 years
    @Rob: How can I implement the same scenario with SOAP request.
  • Rob
    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.