I've built a base class that I use a lot in my iOS app to make calls to web services. I built the base class to make the actual call and this base class is only ever used by another class.
The idea was that should I decide to change from AFNetworking
to something else, I would not need to rewrite code in many other classes and only need to change it in one class.
The rest of my app has no idea how the web service calls are actually made - it just know how to request them. That's it.
I've been thinking of late that maybe I could improve the code.
Here is the base class:
The header file:
BBWebService.h
#import <Foundation/Foundation.h>
@interface BBWebService : NSObject
#pragma mark - Properties
@property (nonatomic) BOOL isBusy;
@property (assign) int webServiceId;
@property (weak, nonatomic) id <NSObject, WebServiceDelegate> delegate;
#pragma mark - Methods
/**
This method is not allowed to instainsiate this class - cannot setup class correctly
*/
-(instancetype) init __attribute__((unavailable("Please use this classes designated initilizer initWithUrl")));
/**
Creates and runs an `NSURLSessionDataTask` with a HTTPS POST request.
@param initWithURL The URL string used to create the request URL.
@param RequestType HTTP request type - "POST" / "PUT" supported for this method only.
@param UrlParameters The parameters to be encoded according to the client request serializer.
@param PostDataValuesAndKeys POST data (NSDictionary)
*/
- (id) initWithURL: (NSString*) url RequestType: (NSString*) requestType PostDataValuesAndKeys: (NSDictionary*) postData UrlParameters: (NSDictionary*) urlParameters;
/**
Creates and runs an `NSURLSessionDataTask` with a HTTP request.
@param initWithURL The URL string used to create the request URL.
@param RequestType HTTP request type - "GET" / "DELETE" supported for this method only.
@param UrlParameters The parameters to be encoded according to the client request serializer.
*/
- (id) initWithURL: (NSString*) url RequestType: (NSString*) requestType UrlParameters: (NSDictionary*) urlParameters;
@end
BBWebservice.m
-(instancetype)initWithURL:(NSString *)url RequestType:(NSString *)requestType UrlParameters:(NSDictionary *)urlParameters
{
[self getSessionManager];
if ([requestType isEqualToString:@"GET"]){
self.isBusy = YES;
[self.manager GET:url parameters:urlParameters
success:^(NSURLSessionDataTask *task, id responseObject){
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
int statusCode = (int)response.statusCode;
[self requestDone:responseObject StatusCode:statusCode];
}failure:^(NSURLSessionDataTask *task, NSError *error){
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
int statusCode = (int)response.statusCode;
[self requestFailed:error StatusCode:statusCode];
}];
//--** DELETE Request --**//
}else if ([requestType isEqualToString:@"DELETE"]){
self.isBusy = YES;
[self.manager DELETE:url parameters:urlParameters success:^(NSURLSessionDataTask *task, id responseObject) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
int statusCode = (int)response.statusCode;
[self requestDone:responseObject StatusCode:statusCode];
} failure:^(NSURLSessionDataTask *task, NSError *error) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
int statusCode = (int)response.statusCode;
[self requestFailed:error StatusCode:statusCode];
}];
}
self.isBusy = NO;
return self;
}
-(instancetype)initWithURL:(NSString *)url RequestType:(NSString *)requestType PostDataValuesAndKeys:(NSDictionary *)postData UrlParameters:(NSDictionary *)urlParameters
{
[self getSessionManager];
self.isBusy = YES;
[self.manager POST:url parameters:urlParameters
success:^(NSURLSessionDataTask *task, id responseObject){
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
int statusCode = (int)response.statusCode;
[self requestDone:responseObject StatusCode:statusCode];
}failure:^(NSURLSessionDataTask *task, NSError *error){
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
int statusCode = (int)response.statusCode;
[self requestFailed:error StatusCode:statusCode];
}];
return self;
}
-(void) requestDone: (id)responseObject StatusCode: (int)statusCode
{
self.isBusy = NO;
if ([responseObject isKindOfClass:[NSData class]]){
if (statusCode == 200)
{
NSData *data = [[NSData alloc]initWithData:responseObject];
if (self.delegate)
{
if ([self.delegate respondsToSelector:@selector(webService:result:)])
{
[self.delegate webService:self result:data];
}
else
{
[self.delegate result:data WebServiceId:self.webServiceId];
}
}
}
else
{
if ([self.delegate respondsToSelector:@selector(httpError:WebServiceId:ErrorCode:Message:)])
{
[self.delegate httpError:nil WebServiceId:self.webServiceId ErrorCode:statusCode Message:@"Web Service Failed!"];
}
else if ([self.delegate respondsToSelector:@selector(webService:httpError:ErrorCode:Message:)])
{
[self.delegate webService:self httpError:nil ErrorCode:statusCode Message:@"Web Service Failed"];
}
}
}
}
-(void)requestFailed: (NSError *)errorMessage StatusCode: (int)statusCode
{
self.isBusy = NO;
NSLog (@"Request failed - Status Code: %d Error: %@", statusCode, errorMessage);
if (statusCode == 0){
UIAlertView *offlineAlertView = [[UIAlertView alloc]initWithTitle:@"" message:@"Your internet connection appears to be offline. Please check your settings and try again" delegate:self
cancelButtonTitle:@"Okay" otherButtonTitles:nil, nil];
[offlineAlertView show];
}
if ([self.delegate respondsToSelector:@selector(webService:connectionError:)])
{
[self.delegate webService:self connectionError: @"Connection Error!"];
}
else
{
//Add extra handling here
}
}
-(AFHTTPSessionManager *)getSessionManager
{
self.manager = [AFHTTPSessionManager manager];
self.manager.responseSerializer = [AFHTTPResponseSerializer serializer];
return self.manager;
}
@end
As you can see - BBWebService
is used to make HTTP POST / GET / DELETE requests. (I need to modify it for PUT requests at some stage)
This class has not given me any issues and has worked just fine.
So to use these methods - I use another class to do the actual request that adds the URL / Dictionary parameters.
Edit:
The protocols
It's just a header file that has these declarations:
#import <Foundation/Foundation.h>
@protocol WebServiceDelegate
@optional
-(void) result:(id) result WebServiceId: (int) webServiceId;
-(void) connectionError:(id) result WebServiceId: (int) webServiceId;
-(void) generalError:(id) result WebServiceId: (int) webServiceId;
-(void) httpError:(id) result WebServiceId: (int) webServiceId ErrorCode: (int) errorCode Message: (NSString *) errorMessage;
-(void) webService:(id) webService result:(id) result;
-(void) webService:(id) webService connectionError:(id) result;
-(void) webService:(id) webService generalError:(id) result;
-(void) webService:(id) webService httpError:(id) result ErrorCode: (int) errorCode Message: (NSString *) errorMessage;
@end
I use the -(void) result:(id) result WebServiceId: (int) webServiceId;
In another class like so:
#pragma mark - BBWSDelegate methods
-(void)result:(id)result WebServiceId:(int)webServiceId
{
if (![result isKindOfClass:[NSData class]]){
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Result was not of type NSData!" userInfo:nil];
}
}
This is in My web service class and is designed to stop execution if for some reason I did not get an NSData
object back.
I then reuse the -(void) result:(id) result WebServiceId: (int) webServiceId;
Again in my XML parsing class - like so:
-(void)result:(id)result WebServiceId:(int)webServiceId
{
if ([result isKindOfClass:[NSData class]]){
NSData *responseData = result;
NSString *xmlData = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSLog (@" BBaseXmlWebService: XML Data Received - %@", xmlData);
self.tbxml = [TBXML newTBXMLWithXMLData:responseData error:nil];
//**-- Start parsing --**//
[self parseResult];
if (self.delegate)
{
[self.delegate result:self.baseResult WebServiceId:webServiceId];
}
}else
{
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Result was not of type NSData!" userInfo:nil];
}
}
And once again in my data class
-(void)result:(id)result WebServiceId:(int)webServiceId
{
[self webServiceEnded:webServiceId];
if (![self handleWebServiceResult:result WebServiceId:webServiceId])
{
// do the default and store in the cache
NSNumber* webServiceIdNumber = [NSNumber numberWithInt:webServiceId];
[self.dataCache setObject:result forKey:webServiceIdNumber];
}
// update the last updated time in the cache
NSTimeInterval time = [NSDate timeIntervalSinceReferenceDate];
NSNumber* timeNum = [NSNumber numberWithDouble:time];
NSNumber* webServiceIdNumber = [NSNumber numberWithInt:webServiceId];
[self.dataLastUpdateTime setObject:timeNum forKey:webServiceIdNumber];
// notify with success
[self notify:webServiceId Result:NOTIFICATION_WEBSERVICE_RESULT_SUCCESS];
}
This way, I get valid, parsed data back into my data class ready to use in the app where I need it.
Edit 2
This is to answer some of nhgrif's questions as my reply is too long for comments.
The class needs to have a delegate as that is how I let another class know the request has been completed.
The second problem you mentioned with the init methods; the reason for the init method making the request is that is all I ever do with this class. Here is an example of how I would use this class:
-(void)fetch:(NSDictionary *)urlParameters
{
if (!self.webService.isBusy){
BBWebService *webService = [[BBWebService alloc]initWithURL:self.url RequestType:GET_REQUEST UrlParameters:urlParameters];
self.webService = webService;
self.webService.webServiceId = wsID;
[self.webService setDelegate:self];
}
}
In this class (ClassB
) where I use this method - I initialize the BBWebService
class with the URL and parameters I need. I also set the delegate for the BBWebService
class here in ClassB
at the same time. ClassB
conforms to the protocols and once BBWebService class has completed the request - ClassB
takes over and handles them - then hands it off to my XML class which parses it and then it moves up the chain to my data store class where I use the parsed data.
This is why I thought it would be best to make sure the BBWebService
class could not be setup with just alloc / init as then you would be using it incorrectly.
Maybe there is a better approach to this?