Question

I am writing a small iOS app that queries a XML REST webservice. The networking framework in use is AFNetworking.

Situation

To query the webservice I subclassed AFHTTPClient:

@interface MyApiClient : AFHTTPClient

and in the implementation I make that available as a singleton:

+ (MyApiClient *)sharedClient {
    static MySharedClient *_sharedClient = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _sharedClient = [[self alloc] initWithBaseUrl:[NSUrl URLWithString:@"http://url.to.the.webservice"]];
    });

    return self;
} 

and in initWithBaseURL I tell AFNetworking to expect XML content:

[self registerHTTPOperationClass:[AFXMLRequestOperation class]];

Now I can call getPatch on the singleton from my ViewController and in the success block start parsing my returned XML. In NSXMLParserDelegate methods in the ViewController I can then pick the parts of the XML I am interested in and do stuff with it.

Problem

I want to have methods in my HTTPClient singleton that handle everything related to the webservice and return data models or list of models instead of XML.

For example I want to do something like this:

ServerModel *status = [[MyApiClient sharedClient] getServerStatus];

The ApiClient would then internally call the webservice, parse the XML and return the model. How can I do that? Normally I would use a delegate that gets called once the XML is parsed, but due to the singleton nature of the ApiClient there could be multiple delegates?

Hope someone can shed light on this, thanks!

Was it helpful?

Solution

(Apologies in advance for this "sort-of" answer, but we're working towards a better solution...)

You need to take a step back and think about your design carefully.

You're having problems because you've got an idea that something in your design needs to be a singleton, but either:

1) that's not actually necessary,

2) something might already exist that does that job for you (e.g. the HTTP lib you're using),

or

3) You're making the wrong thing a singleton, or you haven't portioned out your design into the appropriate parts to work well with the singleton idea

So, can you tell me explicitly why you're going for a singleton approach? Is it just to ensure that only one network request can happen at once? Is there any notion of statefulness in your singleton object? Then I'll update this answer or comment, etc.

(Digression: I would also add that in some cases there might be a true need for a 'strong' singleton -- by which I mean that there really is only one possible instance, and that mechanism is baked right into your object, as you are doing - but this isn't it. The alternative is a 'weak' singleton, by which I mean your core object that actually does the work has a plain init method as usual, but shared access to a common object goes via another object, which is a kind of simple 'factory' that instantiates/holds the shared instance. The advantage of this weak singleton idea is that your code is more re-usable in different contexts - e.g. you could decide to do multiple HTTP requests/sessions concurrently at a later time - and it sometimes makes writing tests less problematic).

OTHER TIPS

Use blocks instead of delegates.

From my ApiClient class:

- (void)getPath:(NSString *)path 
     parameters:(NSDictionary *)parameters 
        success:(void (^)(id response))success 
        failure:(void (^)(NSError *error))failure 
{   
    NSURLRequest *request = [self requestWithMethod:@"GET" path:path parameters:parameters];
    [self enqueueHTTPOperationWithRequest:request success:success failure:failure];
}

-(void)fetchAllUsersSuccess:(void (^)(id))success 
                     failure:(void (^)(NSError *))failure
{
    [self getPath:@"/api/mobile/user/" 
       parameters:nil 
          success:^(id response) {  

                      if([response isKindOfClass:[NSXMLParser class]]){
                          //parse here to new dict
                          success(newDict);
                      } else
                          success(response);

          } failure:^(NSError *error) {
              failure(error);
          }];
}

Now I can use it like:

ServiceApiClient *apiClient = [ServiceApiClient sharedClient];

[apiClient fetchAllUsersSuccess:^(id dict) {
    for (NSDictionary *object in [dict objectForKey:@"objects"]) {
        [ServiceUser addUserFromDictionary:object
                                 inContext:self.managedObjectContext];
    }
    NSError *error= nil;
    [self.managedObjectContext save:&error];
    if (error) {
        NSLog(@"%@", error);
    }
} failure:^(NSError * error) {
    NSLog(@"%@", error);
}];
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top