This example shows how to create a service layer for json based web services.
This is how I developed it.
- Create a basic interface for calling web services
- Create a subclass of correct AFNetworking seralizer class(in this case AFJsonSerializer) and format the response
- For each web service create a singleton subclass of AFHttpSessionManager
- Create a Façade for service layer
For this example I am using free web service used used by
raywenderlich afnetworking tutorial
1 Creating basic interface
In modern software engineering,
we write code for the interface, not for the implementation. So create a protocol named Service as folows
@protocol Service <NSObject>
- (void)callService:(NSDictionary*)parameters withCompletionBlock:(void(^)(NSArray *resultArray, NSError *error))completionBlock;
@end
This has a method called callService and it is the method used to call the web services.
Now we have an interface, which can be used to call web services. I will explain the importance of this when we actually call a web service.
2 Creating a custom json response serializer
Here, we get the response in json format. we need to process this json response in order to get the correct result out of it. For that purpose we can create a subclass of the
AFJSONResponseSerializer class and override it's
responseObjectForResponse as follows. This method is overridden according to the json response we get. This method directly create models from the response and add it to an array
@implementation WeatherServieJasonSerializer
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error {
NSMutableArray *retArray = [[NSMutableArray alloc] init];
NSDictionary *json = [super responseObjectForResponse:response data:data error:error];
NSDictionary *dictionary = json[@"data"];
WeatherInfo *infoRet = [[WeatherInfo alloc] init];
NSArray *arrData = dictionary[@"weather"];
infoRet.tempMaxC = @([((NSString*)((NSDictionary*)arrData[0])[@"tempMaxC"]) integerValue]);
infoRet.tempMaxF = @([((NSString*)((NSDictionary*)arrData[0])[@"tempMaxF"]) integerValue]);
infoRet.tempMinC = @([((NSString*)((NSDictionary*)arrData[0])[@"tempMinC"]) integerValue]);
infoRet.tempMinF = @([((NSString*)((NSDictionary*)arrData[0])[@"tempMinF"]) integerValue]);
[retArray addObject:infoRet];
return retArray;
}
@end
3 Creating a singleton subclass of AFHttpSessioManager to invoke web services.
For each web service you have, you need to create a separate class like this. In addition this class conform to the Service protocol and implements its
callService method. Following is the code for the class.
.h file
#import "AFHTTPSessionManager.h"
#import "Service.h"
@interface WetherWebService : AFHTTPSessionManager<Service>
+ (WetherWebService*)getSharedInstance;
@end
.m file
#import "WetherWebService.h"
#import "WeatherServieJasonSerializer.h"
static NSString * const baseURLString = @"http://www.raywenderlich.com/demos/weather_sample/";
@implementation WetherWebService
+ (WetherWebService*)getSharedInstance {
static WetherWebService *sharedInstance = nil;
static dispatch_once_t token;
dispatch_once(&token, ^{
sharedInstance = [[WetherWebService alloc] initWithBaseURL:[NSURL URLWithString:baseURLString]];
sharedInstance.responseSerializer = [WeatherServieJasonSerializer serializer];
});
return sharedInstance;
}
- (void)callService:(NSDictionary*)parameters withCompletionBlock:(void(^)(NSArray *resultArray, NSError *error))completionBlock {
[self GET:@"weather.php" parameters:parameters success:^(NSURLSessionDataTask *task, id responseObject) {
completionBlock(responseObject, nil);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
completionBlock(nil, error);
}];
}
@end
4 Creating the service layer façade.
This is an implementation of
façade design pattern. This creates an abstraction layer between the web service layer and the rest of the application. This is the source of ServiceLayerFacade.h file
@interface ServiceLayerFacade : NSObject
+ (ServiceLayerFacade*)getSharedInstance;
- (id<Service>)getWeatherService;
@end
#import "ServiceLayerFacade.h"
#import "WetherWebService.h"
@implementation ServiceLayerFacade
+ (ServiceLayerFacade*)getSharedInstance {
static ServiceLayerFacade *sharedInstance = nil;
static dispatch_once_t token;
dispatch_once(&token, ^{
sharedInstance = [[ServiceLayerFacade alloc] init];
});
return sharedInstance;
}
- (id<service>)getWeatherService {
return [WetherWebService getSharedInstance];
}
This is how these web services should be called.
NSDictionary *parameters = @{@"format": @"json"};
ServiceLayerFacade *serviceLayer = [ServiceLayerFacade getSharedInstance];
id<Service> weatherWebService = [serviceLayer getWeatherService];
[weatherWebService callService:parameters withCompletionBlock:^(NSArray *resultArray, NSError *error) {
if (resultArray[0] != nil) {
id<Tablelayout> tableModel = resultArray[0];
_tableArray = [tableModel getTableRepresentation];
[_weatherTable reloadData];
}
}];
Here, since we are using interfaces and a facade, we can completely hide the implementation details of the web service layer. Web service caller only knows of the uniform interface method to call the web service. He does not know the classes used to call the web service, the way response is processed etc. What he gets is model classes that can be directly used in his code.
Now I will explain the beauty of this approach. We use Service interface to access the web service. And we do not know how the web service is called behind the scene and how the response is processed. Because of this we can change everything related to web service calling and processing without affecting rest of the layers.
Presenting the data
This is our model class
#import
@interface WeatherInfo : NSObject
@property NSNumber *tempMaxC;
@property NSNumber *tempMaxF;
@property NSNumber *tempMinC;
@property NSNumber *tempMinF;
@end
Now we have the data retrieved from the web service stored in our model classes. Now how to present it? You can add a method to the model class to get some sort of array and show it in a table.
But this is an extremely bad idea. Because a model class should not know how the data is presented. So here our basic problem is adding behavior to the model object, without affecting behavior of other objects from the same class. What is the design pattern we can use to achieve this? The answer is
decorator pattern
Implementation of decorator pattern
I normally use categories to implement decorator pattern in objective c. In addition I declare category methods in a protocol as well. This protocol acts as an interface
#import
@protocol TableLayout <NSObject>
-(NSArray*)getTableRepresentation;
@end
This is the category of the model class.
.h file
#import "WeatherInfo.h"
#import "TableLayout.h"
@interface WeatherInfo (TableRepresentation)<TableLayout>
@end
This is the .m file
#import "WeatherInfo+TableRepresentation.h"
@implementation WeatherInfo (TableRepresentation)
-(NSArray*)getTableRepresentation {
return [[NSArray alloc] initWithObjects:self.tempMaxC,self.tempMaxF,self.tempMinC,self.tempMinF, nil];
}
@end
Then in code via the TableLayout interface I am getting the table representation as follows.
id<Tablelayout> tableModel = resultArray[0];
_tableArray = [tableModel getTableRepresentation];
[_weatherTable reloadData];
This is how I implemented the service layer. The complete example of this tutorial is available in