Isolating Networking module inside your app
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.
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, …
// 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:
// 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:
// 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:
// 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.