iosobjective-cnsurldelegation

Are there cleaner methods to handling async http requests than delegation in Objective-C?


I'm building an iPhone app that heavily leverages a web service I wrote in PHP. Right now I have a file (API.h/API.m) that has methods which call PHP functions on my web service through NSURLConnection.

Methods like:

-(void) mediaAdd:(UIImage *)image withDelegate: (id<apiRequest>) delegate;
-(void) mediaGet:(NSString *)imageURL withDelegate: (id<apiRequest>) delegate;

These methods are called by controllers in the app (controllers defined by the MVC pattern), and the methods take in the controller as the delegate.

Once the request from the called method in API.h/API.m has finished, the NSURLConnection delegate method in API.h/API.m that is called when the request is finished, then calls the delegate method...

-(void) serverResponse:(NSDictionary *) response fromCall:(NSString *)call;

...which is of course executed on the controller that is calling the method in API.h/API.m.

My problem, or rather disorganization arises when a controller needs to make more than one API call. Because I only have the single delegate method, I differentiate between different calls by using the call parameter in the serverResponse method.

Here's a skeleton of an instance of the serverResponse method:

//API Delegate
-(void)serverResponse:(NSDictionary *)response fromCall:(NSString *)call {
    bool local = false;
    NSString *callbackString;
    if (local) {
        callbackString = @"http://localhost/";
    } else {
        callbackString = @"http://secret.com/";
    }
    if ([call isEqualToString:[NSString stringWithFormat:@"%@user/add",callbackString]]) {
        //user/add was called
        if (![[response objectForKey:@"status"] isEqualToString:@"fail"]) {
            if ([[response objectForKey:@"status"] isEqualToString:@"fail"]) {
                if ([[response objectForKey:@"reason"] isEqualToString:@"udid already taken"]) {
                     //UDID taken
                }
            } else {
                
                //UDID not taken, we're good to go
                

                
            }
            
        } else {
            NSLog(@"fail");
        }
    } else if([call isEqualToString:[NSString stringWithFormat:@"%@moment/get",callbackString]]){
        //moment/get was the api call
    } else if([call isEqualToString:[NSString stringWithFormat:@"%@printPictures/printPic",callbackString]]){
        
        //printPictures/printPic was the api call
    } else {
        NSLog(@"wrong call");
    }
    
    
    
}

My question is, should I go ahead and take the plunge of making a delegate method for each API call? Or am I overthinking this, and there is actually a really simple method of handling this. Perhaps a design pattern or structure?


Solution

  • I do this kind of thing by having a separate download class with a callback block. The controller creates an instance of the class, and receives the result in the callback, after which, the download class is deallocated. Each call you need to make creates its own instance of the Downloader class, so you don't need to keep track of which response goes with which call.

        Downloader *dl = [Downloader new];
        NSURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"...."]] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
    
        [dl downloadWithRequest:(NSURLRequest *) request completion:^(NSData *data, NSError *error) {
            if (!error ) {
                // do whatever you need to do with the raw data here
                NSLog(@"got the data");
            }else{
                NSLog(@"%@",error);
            }
        }];
    

    Here is how I implemented the Downloader class.

    typedef void(^compBlock)(NSData *data, NSError *error);
    
    @interface Downloader : NSObject <NSURLConnectionDataDelegate>
    
    -(void)downloadWithRequest:(NSURLRequest *) request completion:(compBlock) completion;
    
    @end
    

    The implementation file,

    @interface Downloader()
    @property (copy,nonatomic) compBlock completionHandler;
    @property (strong,nonatomic) NSMutableData *receivedData;
    @end
    
    @implementation Downloader
    
    -(void)downloadWithRequest:(NSURLRequest *) request completion:(compBlock)completion {
        NSURLConnection *con = [NSURLConnection connectionWithRequest:request delegate:self];
        if (con) {
            self.receivedData = [NSMutableData new];
            self.completionHandler = completion;
        }
    }
    
    
    -(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
        self.receivedData.length = 0;
    }
    
    
    
    -(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
        [self.receivedData appendData:data];
    }
    
    
    
    -(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
        self.completionHandler(nil, error);
    }
    
    
    
    -(void)connectionDidFinishLoading:(NSURLConnection *)connection {
        self.completionHandler(self.receivedData, nil);
    }