0

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!

dornad
  • 1,274
  • 3
  • 17
  • 36
  • Why do you need delegates if you've got callback blocks? – CodaFi Sep 12 '13 at 20:25
  • At this point there is no particular reason why I'm using delegates instead of just the callback blocks. What would be the advantages of using callback blocks? – dornad Sep 12 '13 at 20:46
  • Also, a coworker mentioned that callbacks blocks are not as readable as delegates. Personally, I don't have any preference, but he's the main user of my classes, so I'd like to accomodate to him, if possible. – dornad Sep 12 '13 at 20:50
  • Really? He's whining about readability when you have to design 36 different delegates to interact with this API at all? – CodaFi Sep 13 '13 at 02:49
  • A REST API wouldn't have a `users/create` endpoint, you would just POST to `users/` to create one, PUT to update one, etc. I would recommend using callback blocks instead of delegate methods because this approach avoids possible race conditions. – Aaron Brager Sep 13 '13 at 15:28

2 Answers2

1

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).

e1985
  • 6,239
  • 1
  • 24
  • 39
  • Injection seems to be an interesting approach. I'd have to look into best practices for injection in objective-c though. – dornad Sep 16 '13 at 15:56
0

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...

ipinak
  • 5,739
  • 3
  • 23
  • 41
  • I thought that each subclass of DSAPI should have its own delegate class. To me it seems that is a bad idea to have a monolithic delegate that has every callback in my app. Dividing it by concerns seem to be a good idea IMO. – dornad Sep 12 '13 at 22:27
  • How exactly am I moving into a infinite loop of singletons? My code in DSAPI#sharedClient should be returning a singleton instance for the client, right? – dornad Sep 12 '13 at 22:29
  • I am commenting your thought process. It's what happened to me once, I began with one (for networking like you) and then ended up have 5 for something simple. So don't overdo it with the singletons. Design patterns (like Singleton) are good but they are not applicable to every solution. – ipinak Sep 13 '13 at 08:12