Isolating Networking module inside your app

08 Nov 2012

There's been times I had to switch over from one Networking library to another, e.g. from ASIHttpRequest to AFNetworking or even SVHTTPRequest
Sometimes the web side changes API endpoints, and it's a pain if you have to change them all over your app. You may want to isolate them in one place, so there's no effort needed to make these transitions.

Here's how I prefer to do it.
<!-- more -->

Server class

Make a Server class that represents the real server (API endpoint). The base url is defined only on one place, and the other paths are separate constants. That object also provides a proxy request methods, like GET, POST, PUT, ...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Server.h
#import <Foundation/Foundation.h>

static NSString *const BASE_URI = @"http://supermar.in/api";
static NSString *const NEWS_PATH = @"news";
static NSString *const REVIEWS_PATH = @"reviews";
static NSString *const SOME_STUPID_PATH = @"stupid/path/that_changes.php";


@interface Server : NSObject

+ (void)GET:(NSString *)path parameters:(NSDictionary *)params
                              completion:(void (^)(id JSON, NSError *error))finished;

+ (void)POST:(NSString *)path parameters:(NSDictionary *)params
                              completion:(void (^)(id JSON, NSError *error))finished;

// this one is here for performing some custom requests, e.g. TWRequest
+ (void)performRequest:(NSURLRequest *)request
            completion:(void (^)(id JSON, NSError *error))finished;

@end

This is just an stripped down example of implementing a small JSON API. You may want to customize the server responses by your needs. And the best of all is, if the server switches from JSON to XML or vice versa, the only place to adapt is this class.
Your code should not worry about the server.

Here's how the Server's implementation with AFNetworking looks like:

1
2
3
4
5
6
7
8
9
10
11
// Server.m
+ (void)GET:(NSString *)path parameters:(NSDictionary *)params
                             completion:(void (^)(id JSON, NSError *))finished {

  [[AFJSONRequestOperation JSONRequestOperationWithRequest:[self requestWithParams:params]
      success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
          finished(JSON, nil);
      }
      failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
          finished(JSON, error);
      }] start];

Let's say you want to completely get rid of AFNetworking and replace it with SVHTTPRequest. If you had many controllers communicating with the web service, you'd need to change all those files, and adapt the new interfaces.

With this approach, this one's easy:

1
2
3
4
5
6
7
8
9
// Server.m
+ (void)GET:(NSString *)path parameters:(NSDictionary *)params
                             completion:(void (^)(id JSON, NSError *))finished {

   [SVHTTPRequest GET:path
           parameters:params
           completion:^(id response, NSHTTPURLResponse *urlResponse, NSError *error) {
               finished(response, error);
           }];

Here's an example of a controller that's talking with the web service:

1
2
3
4
5
6
7
8
9
10
11
12
13
// ReviewsManager.m
- (void)findReviewsForRestaurant:(Restaurant *)restaurant
                       andCourse:(Course *)course {

    [Server GET:REVIEWS_PATH
        parameters:@{
            @"place_id" : restaurant.restaurantId,
            @"course_id" : @(course.courseID)
        }
        completion:^(id response, NSError *error) {
            [self findReviewsRequestFinished:response];
    }];
}

You may push this even further and pull out parameter names to constants.

Testing

One more benefit of doing this is that you can switch out the real Server and use the fake implementation in your tests. The other side of your code shouldn't notice any difference.