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

∥☆過路亽.° 提交于 2019-12-12 04:46:30

问题


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?

Thank you! - Mike


回答1:


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);
}



回答2:


My problem, or rather disorganization arises when a controller needs to make more than one API call.

For this kind of thing, you should really look at NSURLSession and related classes. NSURLSession provides methods like -sendAsynchronousRequest:queue:completionHandler: that let you complete tasks without having to provide your own delegate object -- the session will provide a default delegate for you, and the default delegate will call your completion handler when the task is done.

You still have the option of providing your own delegate, of course, and each task has an identifier so that your delegate can keep track of which task is which more easily than you can with NSURLConnection.

I won't go into all the benefits of NSURLSession here because you can read about them yourself, but they're significant. If you're writing new code, you almost certainly should be using NSURLSession instead of NSURLConnection.



来源:https://stackoverflow.com/questions/25669761/are-there-cleaner-methods-to-handling-async-http-requests-than-delegation-in-obj

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!