Passing blocks to a AFNetworking method?

2019-03-29 11:16发布

问题:

-(void )getDataFromServer: (NSMutableDictionary *)dict
 {

 NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/doSomething",MainURL ]];
 [AFJSONRequestOperation addAcceptableContentTypes:[NSSet setWithObject:@"text/html"]];

 AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
 NSMutableURLRequest *request = [httpClient requestWithMethod:@"POST" path:nil parameters:dict];

 AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request
 success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON)
 {
     _myArray = JSON;
     [_myTableView reloadData]; //Or do some other stuff that are related to the current `ViewController`
 }
 failure:^(NSURLRequest *request , NSURLResponse *response , NSError *error , id JSON)
 {
     NSLog(@"request: %@",request);
     NSLog(@"Failed: %@",[error localizedDescription]);
 }];

 [httpClient enqueueHTTPRequestOperation:operation];
 }

I'm using the above code in 7 different places in one of my apps. The exact chunk of code is duplicated in 7 of my ViewControllers. What I normally used to do is to put the method I want to use in a NSObject class and allocate and use it when ever I need but because the above is Async and using blocks I can't just return the JSON to the ViewController who called it and have to copy & paste the above method in every ViewController I need it in.

My goal is to have the above in only one place in my app and still be able to call it from different ViewControllers around my app and get the data I need. I'd like to avoid using an observer such as NSNotification or KVO and looking for a more elegant solution. After some reading I noticed the possibility to pass blocks around. Is that a possible solution with the above? A code example would be appreciated.

回答1:

Factorize the API call to something like

+ (void)getDataFromServerWithParameters:(NSMutableDictionary *)params completion:(void (^)(id JSON))completion failure:(void (^)(NSError * error))failure {
     NSString * path = @"resources/123";
     NSMutableURLRequest *request = [self.httpClient requestWithMethod:@"POST" path:path parameters:params];
     AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
        if (completion)
            completion(JSON);
     } failure:^(NSURLRequest *request , NSURLResponse *response , NSError *error , id JSON) {
        if (failure)
            failure(error);
     }];

     [httpClient enqueueHTTPRequestOperation:operation];
 }

You can place this method in a utility class like XYAPI and just invoke it from your controllers like

 [XYAPI getDataFromServer:params completion:^(id JSON){
     // do something, for instance reload the table with a new data source
     _myArray = JSON;
     [_myTableView reloadData];
 } failure:^(NSError * error) {
    // do something
 }];

Also you don't need to spawn a new AFHTTPClient at every request. Configure and use a shared one in the XYAPI class. Something like

+ (AFHTTPClient *)httpClient {
    static AFHTTPClient * client = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        client = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:@"http://foo.com/api/v1/"]];
        [AFJSONRequestOperation addAcceptableContentTypes:[NSSet setWithObject:@"text/html"]];
    });
    return client;
}

Note that this implementation is already used in the above example.
self in the context of a class method is the class itself, so self.httpClient is indeed resolved at runtime as [XYAPI httpClient].