AFNetworking AFHTTPClient Subclassing, Singleton Client and Delegates

1.9k views Asked by At

I'm writing a REST API layer with AFNetworking for an iOS project. I have some questions about what I have written so far, so I'm turning to stackoverflow for some guidance/answers.

Here are the guidelines of what I'm trying to achieve:

  • A base class (DRAPI : AFHTTPClient) that will initialize a singleton client, just as the cocoadocs for AFHTTPClient recommend.
  • A "base" delegate for DRAPI: DRAPIDelegate, that holds methods for displaying errors in an unified format.
  • Subclasses of DRAPI that deal with certain routes of my REST API. For example, CRUD operations on Users are responsability of DRUserAPI, which is a child class of DRAPI.
  • Each subclass of DRAPI has its own delegate. For DRUserAPI, there's DRUserAPIDelegate, which extends DRAPIDelegate.

Here is a quick example of how things are written so far:

DRAPI.h

@interface DRAPI : AFHTTPClient

- (void) apiGetCallWithRoute:(NSString*)route
                  parameters:(NSDictionary*)parameters
                   onSuccess:(void(^)(id))successBlock
                     onError:(void(^)(NSArray* errors))errorBlock;

@end

@protocol DRAPIDelegate <NSObject>

-(void) DRAPIErrorFromServer:(NSArray*)errors;

@end

DRAPI.m

@implementation DRAPI

+(DRAPI*) sharedClient
{
  static DRAPI *aSharedClient = nil;
  static dispatch_once_t onceToken;
  dispatch_once(&once_token, ^{
    _sharedClient = [DRAPI alloc] initWithBaseURL:[NSURL URLWithString:@"http://127.0.0.1:3000/api"];
  });
  return aSharedClient;
}

-(id) initWithBaseURL:(NSURL *)url
{
  self = [super initWithBaseURL:url];
  if (self) {
     // configuration goes here... skipping because it is not important.
  }
  return self;
}

#pragma mark - Helper methods for Server Calls

- (void) apiGetCallWithRoute:(NSString*)route
                  parameters:(NSDictionary*)parameters
                   onSuccess:(void(^)(id))successBlock
                     onError:(void(^)(NSArray* errors))errorBlock 
{
  [[DRAPI sharedClient] getPath:route
                    parameters:addAuthenticationParametersTo(parameters)
                       success:^(AFHTTPRequestOperation *operation, id responseObject) {
                         successBlock(responseObject);
                       }
                       failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                         errorBlock( processError() );
                       }];
}

@end

DRUserAPI.h

@interface DRUserAPI: DRAPI

@property (weak, nonatomic) id<DRUserAPIDelegate>delegate;

+(DRUserAPI*) APIWithDelegate:(id<DRUserAPIDelegate>)delegate;

-(void) createUser:(NSString*)username password:(NSString*)password;

// ... more methods would be declared here...

@end

@protocol DRUserAPIDelegate <NSObject, DRAPIDelegate>

-(void) DRUserAPIOnUserCreated:(MyUserModel*)newUser;

// more delegate methods would be here...

@end

DRUserAPI.m

@implementation DRUserAPI

@synthesize delegate;

+(DRUserAPI*) APIWithDelegate:(id<DRUserAPIDelegate>)delegate 
{
  DRUserAPI * client = [DRUserAPI new];
  client.delegate = delegate;
  return client;
}

-(void) createUser:(NSString*)username password:(NSString*)password
{
  [self apiGetCallWithRoute:@"users/create"
                 parameters:@{@"username" : username, @"password": password}
                  onSuccess:^(id response) {
                    NSDictionary *parsedJSON = response;
                    [delegate DRUserAPIOnUserCreated:[MyUserModel newModelFromDictionary:parsedJSON];
                  }
                  onError:^(NSArray *errors) {
                    [delegate DRAPIErrorFromServer:errors];
                  }];
}

@end

A fellow co-worker brought to my attention that delegates and singletons do not mix. I still want to manage delegates, though. I'm thinking that good solution would be pass the singleton instance of the delegate to the method I'm calling inside the API subclass.

Is this a good idea?

Thanks!

2

There are 2 answers

1
e1985 On BEST ANSWER

I prefer an implementation based on composition instead of subclassing, even if the AFNetworking docs recommend to subclass AFHTTPClient.

I would inject the AFHTTPClient in the DRAPI and this one in the DRUserAPI, making both of them simple subclasses of NSObject. A flat design is cleaner IMO and it makes it easier to unit test your classes.

Instead of having singletons you can create an injector class responsible of creating your whole object graph, calling it only in your application delegate.

For this you should use a block based API instead of delegates since you would only have one instance of DRAPI and you don't want to set its delegate before any call to it (you could have another class like DRUserAPI where you inject the DRAPI instance).

3
ipinak On

It's not perfect, but it works. Why so many delegates? It seems that you moving to a infinite loop of singletons. I think you should stop...