背景
网络请求经常会出现一部分公共业务,比如加密,缓存方式等等,所以我们需要对网络做一层偏业务的封装,既可以统一管理一些公共业务,也可以方便网络库的升级AFNetworking。
随着需求的增加网络层对应调整
基础需求
提供URL、参数、简单实现Get、Post、Put、Delete实现,成功返回结果,失败返回错误原因
typedef NS_ENUM (NSUInteger, APIRequestType)
{
APIRequestTypeGet,
APIRequestTypePost,
APIRequestTypePut,
APIRequestTypeDelete,
};
@interface APIManager : NSObject
+ (void)loadDataWithURLString:(NSString *)urlString
params:(NSDictionary *)params
requestType:(APIRequestType)requestType
completeHandler:(void(^)(BOOL success, id result, NSString *message))completeHandler;
@end
使用举例
[APIManager loadDataWithURLString:@"https://192.168.1.1:8080/login"
params:@{@"name":@"1000", @"password": @"123456"}
requestType: APIRequestTypeGet
completeHandler:^(BOOL success, id result, NSString *message) {
}];
每次都要写URLString的拼接比较麻烦,而且还要有Debug和Release的判断,很自然会把固定的host部分https://192.168.1.1:8080/
统一管理起来,而对外暴露的接口可以优化为
@interface APIManager : NSObject
+ (void)loadDataWithAction:(NSString *)action
params:(NSDictionary *)params
requestType:(APIRequestType)requestType
completeHandler:(void(^)(BOOL success, id result, NSString *message))completeHandler;
@end
使用举例
[APIManager loadDataWithAction:@"login"
params:@{@"name":@"1000", @"password": @"123456"}
requestType: APIRequestTypeGet
completeHandler:^(BOOL success, id result, NSString *message) {
}];
接下来我们简单对host的管理介绍一下
- 如果整个项目host是固定的而且在一个环境(debug、release环境)中有且只有一个,那么我们可以使用宏定义。比如
#ifdef DEBUG
#define APIManagerHost @"http://192.168.40.100:8085/"
#else
#define APIManagerHost @"https://www.google.com:8085/"
#endif
- 如果我们的host需要动态变化,我们一般会把host相关的业务做成一个单例,这样就比较方便的切换了。假设我们有这样的需求,开发环境、生产环境、预发布环境可以手动自由切换。
typedef NS_ENUM(NSInteger, APIServiceType)
{
APIServiceTypeDebug, //开发环境
APIServiceTypeRelease, //生产环境
APIServiceTypeReleaseDebug, //预发布环境
};
@interface APIService : NSObject
+ (instancetype)shareInstance;
///默认会根据build configuration的选择自动设置
@property (nonatomic, assign) APIServiceType serviceType;
///当前host地址
- (NSString *)currentHost;
@end
以上的方案针对比较简单的网络需求这样设计基本上就可以解决问题,而且给其他人使用理解成本也比较小。实际情况我们的需求是在不断增加的
比如:我们要对部分接口进行加密,很自然就会想到下面处理
@interface APIManager : NSObject
+ (void)loadDataWithAction:(NSString *)action
params:(NSDictionary *)params
requestType:(APIRequestType)requestType
completeHandler:(void(^)(BOOL success, id result, NSString *message))completeHandler;
+ (void)loadDataWithAction:(NSString *)action
params:(NSDictionary *)params
requestType:(APIRequestType)requestType
shouldEncrypt:(BOOL)shouldEncrypt
completeHandler:(void(^)(BOOL success, id result, NSString *message))completeHandler;
@end
我们接着增加需求,部分接口需要指定超时时间
@interface APIManager : NSObject
+ (void)loadDataWithAction:(NSString *)action
params:(NSDictionary *)params
requestType:(APIRequestType)requestType
completeHandler:(void(^)(BOOL success, id result, NSString *message))completeHandler;
+ (void)loadDataWithAction:(NSString *)action
params:(NSDictionary *)params
requestType:(APIRequestType)requestType
shouldEncrypt:(BOOL)shouldEncrypt
completeHandler:(void(^)(BOOL success, id result, NSString *message))completeHandler;
+ (void)loadDataWithAction:(NSString *)action
params:(NSDictionary *)params
requestType:(APIRequestType)requestType
timeOut:(CGFloat)timeOut
completeHandler:(void(^)(BOOL success, id result, NSString *message))completeHandler;
+ (void)loadDataWithAction:(NSString *)action
params:(NSDictionary *)params
requestType:(APIRequestType)requestType
shouldEncrypt:(BOOL)shouldEncrypt
timeOut:(CGFloat)timeOut
completeHandler:(void(^)(BOOL success, id result, NSString *message))completeHandler;
@end
当产品经理提出要支持缓存的时候,发现这样加接口有些难受了。
为了不需要持续修改接口,我们可能会想到把功能类的也使用一个字典来存储,这样就可以不需要每次都去改接口了
extern NSString *const APIManagerFeatureKeyEncrypt; //加密,BOOL值
extern NSString *const APIManagerFeatureKeyTimeOut; //超时,CGFloat值
extern NSString *const APIManagerFeatureKeyCache; //缓存,BOOL值
//内存缓存、硬盘缓存、缓存时间等等...
@interface APIManager : NSObject
+ (void)loadDataWithAction:(NSString *)action
params:(NSDictionary *)params
requestType:(APIRequestType)requestType
features:(NSDictionary *)features
completeHandler:(void(^)(BOOL success, id result, NSString *message))completeHandler;
@end
这样设计之后,每次增加功能,暂时有了统一解决方式。
我们再来分析一下回调块。
在支持缓存数据的时候,我们有可能需要知道当前返回的数据是不是来着缓存,这时会发现BOOL success, id result, NSString *message
的结构是很难满足需求的,如果通过增加个返回变量BOOL success, BOOL cache, id result, NSString *message
来解决,根据上面的经验,很容易出现需要持续修改接口,比如,需要知道缓存来着内存还是硬盘,或者需要知道这次请求用了多长时间,这时很容易确定通过增加变量很难扩展;这时可能会想到要不也像features一样返回,也定义一些key,比如下面的方式
extern NSString *const APIManagerFeatureKeyEncrypt; //加密,BOOL值
extern NSString *const APIManagerFeatureKeyTimeOut; //超时,CGFloat值
extern NSString *const APIManagerFeatureKeyCache; //缓存,BOOL值
//内存缓存、硬盘缓存、缓存时间等等...
extern NSString *const APIManagerExtendDataKeyCacheRAM; //内存缓存,BOOL值
extern NSString *const APIManagerExtendDataKeyCacheNative; //硬盘缓存,BOOL值
extern NSString *const APIManagerExtendDataKeyRequstTimeInterval; //请求时间,CGFloat值
//等等...
@interface APIManager : NSObject
+ (void)loadDataWithAction:(NSString *)action
params:(NSDictionary *)params
requestType:(APIRequestType)requestType
features:(NSDictionary *)features
completeHandler:(void(^)(BOOL success, id result, NSDictionary *extend, NSString *message))completeHandler;
@end
考虑到字典的局限性,不能有基础类型(int,CGFloat...)。我们可能会想到,把字典换成对象,于是简单封装成请求一个对象,返回一个对象。比如下面的实现方式
@interface APIRequest : NSObject
@property (nonatomic, strong) NSString *action;
@property (nonatomic, strong) NSDictionary *params;
@property (nonatomic, assign) APIRequestType requestType; //默认Get
@property (nonatomic, strong) NSString *host; //默认APIService.currentHost
@property (nonatomic, assign) BOOL shouldEncrypt; //默认NO
@property (nonatomic, assign) BOOL shouldCache; //默认NO
@property (nonatomic, assign) CGFloat timeOut; //默认10s
@end
@interface APIResponse : NSObject
@property (nonatomic, assign) BOOL success;
@property (nonatomic, strong) id jsonObject;
@property (nonatomic, strong) NSString *message;
@property (nonatomic, assign) BOOL fromCache;
@end
@interface APIManager : NSObject
+ (void)loadDataWithRequest:(APIRequest *)request
completeHandler:(void(^)(APIResponse *response))completeHandler;
@end
这样设计完,加功能什么的只要通过APIRequest中加功能就行了,使用起来虽然没有最上面那么直观,但好歹也可以基本上解决问题了,而且接口也不需要变了。但是随着功能的增加,APIRequest和APIManager会越来越杂,可读性越来越差。比如我们增加公共请求参数、公共header参数、取消功能、XX模块统一使用加密方式1,YY模块统一使用加密方式2 等等...
@interface APIRequest : NSObject
@property (nonatomic, strong) NSString *action;
@property (nonatomic, strong) NSDictionary *params;
@property (nonatomic, strong) NSDictionary *headerParams; //默认nil
@property (nonatomic, assign) APIRequestType requestType; //默认Get
@property (nonatomic, strong) NSString *host; //默认APIService.currentHost
@property (nonatomic, strong) NSDictionary *commonParams; //默认nil
@property (nonatomic, strong) NSDictionary *commonHeaderParams; //默认nil
@property (nonatomic, assign) BOOL shouldEncrypt; //默认NO
@property (nonatomic, assign) BOOL shouldCache; //默认NO
@property (nonatomic, assign) CGFloat timeOut; //默认10s
//...
@end
这时我们开始考虑如何给APIRequest和APIManager瘦身,可以把同一类的业务拆分出去。
APIRequest拆分
host、commonParams、commonHeaderParams这类不怎么变的可以使用配置类统一管理
@interface APIConfig : NSObject
@property (nonatomic, strong) NSString *host; //默认APIService.currentHost
@property (nonatomic, strong) NSDictionary *commonParams; //默认nil
@property (nonatomic, strong) NSDictionary *commonHeaderParams; //默认nil
@end
@interface APIRequest : NSObject
@property (nonatomic, strong) NSString *action;
@property (nonatomic, strong) NSDictionary *params;
@property (nonatomic, strong) NSDictionary *headerParams; //默认nil
@property (nonatomic, assign) APIRequestType requestType; //默认Get
@property (nonatomic, assign) BOOL shouldEncrypt; //默认NO
@property (nonatomic, assign) BOOL shouldCache; //默认NO
@property (nonatomic, assign) CGFloat timeOut; //默认10s
@end
APIManager拆分
如果使用类方法做统一入口,拆分起来只能在实现中做很多工具方法,统一的入口还是集中在.m文件中,如果是某些模块定制的请求方式,其他模块不应该暴露的,于是我们会联想到继承
这时我们可以很好的定制一类API了,简单封装如下
typedef NS_ENUM (NSUInteger, APIRequestType)
{
APIRequestTypeGet,
APIRequestTypePost,
APIRequestTypePut,
APIRequestTypeDelete,
};
@interface APIConfig : NSObject
@property (nonatomic, strong) NSString *host;
@property (nonatomic, strong) NSDictionary *commonParams;
@property (nonatomic, strong) NSDictionary *commonHeaderParams;
@end
@interface APIRequest : NSObject
@property (nonatomic, strong) NSString *action;
@property (nonatomic, strong) NSDictionary *params;
@property (nonatomic, assign) APIRequestType requestType;
@property (nonatomic, strong) NSDictionary *headerParams; //默认为nil
@property (nonatomic, assign) BOOL shouldEncrypt; //默认NO
@property (nonatomic, assign) BOOL shouldCache; //默认NO
@property (nonatomic, assign) CGFloat timeOut; //默认10s
@end
@interface APIResponse : NSObject
@property (nonatomic, assign) BOOL success;
@property (nonatomic, strong) id jsonObject;
@property (nonatomic, strong) NSString *message;
@property (nonatomic, assign) BOOL fromCache;
@end
@interface APIManager : NSObject
@property (nonatomic, readonly) APIConfig *config;
@property (nonatomic, readonly) APIRequest *request;
@property (nonatomic, readonly) APIResponse *response;
@property (nonatomic, readonly) NSURLSessionTask *currentTask;
- (instancetype)initWithConfig:(APIConfig *)config;
- (void)loadDataWithRequest:(APIRequest *)request
completeHandler:(void(^)(APIResponse *response))completeHandler;
- (void)cancel;
@end
接口修改成这样,扩展性相比之前过程化的设计还是好很多,增加功能也不在需要改接口。为了给上层定制提供更方便的定制,我们可以增加一些通用的拦截器。
@interface APIManager : NSObject
@property (nonatomic, readonly) APIConfig *config;
@property (nonatomic, readonly) APIRequest *request;
@property (nonatomic, readonly) APIResponse *response;
@property (nonatomic, readonly) NSURLSessionTask *currentTask;
- (instancetype)initWithConfig:(APIConfig *)config;
- (void)loadDataWithRequest:(APIRequest *)request
completeHandler:(void(^)(APIResponse *response))completeHandler;
- (void)cancel;
- (BOOL)willSendRequest:(APIRequest *)request; //将要发起请求,返回NO,进入失败回调
- (void)didSendRequest:(APIRequest *)request; //发起请求完成
- (BOOL)willSuccessCallBack:(APIResponse *)response; //与服务器交互成功,返回NO,进入失败回调
- (void)didSuccessCallBack:(APIResponse *)response; //回调完成
- (BOOL)willFailureCallBack:(APIResponse *)response; //与服务器交互失败,返回NO,不回调
- (void)didFailureCallBack:(APIResponse *)response; //回调完成
@end
XX模块定制特殊的加密方式1
@interface XXAPIRequest : NSObject
///默认NO
@property (nonatomic, assign) BOOL shouldEncrypt1;
@end
@interface XXAPIManager: APIBaseManager
@end
@implementation XXAPIManager
- (instancetype)init
{
APIConfig *config = [[APIConfig alloc] init];
config.host = [XXAPIService shareInstance].currentHost;
config.commonParams = @{};
config.commonHeaderParams = @{};
return [self initWithConfig:config];
}
- (BOOL)willSendRequest:(APIRequest *)request
{
if (self. shouldEncrypt1)
{
//加密方式1...
}
return YES;
}
@end
XX模块业务调用
NSDictionary *params = @{@"name":@"1000", @"password": @"123456"};
XXAPIRequest *request = [XXAPIRequest requestWithAction:@"login" params:params];
request.shouldEncrypt1 = YES;
XXAPIManager *api = [[XXAPIManager alloc] init];
[api loadDataWithRequest:request completeHandler:^(APIResponse *response) {
}];
为了让业务调用的更舒服,我们还可以通过拦截器做一些通用的校验,假设XX模块的服务器返回的通用结构为以下方式
{
"code": 100,
"message": "请求成功",
"result": {
"key1": "value1",
"key2": "value2"
}
}
当code为100的时候,请求成功,业务调用完全没有必要每次判断code的值,完全可以在XXAPIManager
这一层做一次校验
@implementation XXAPIManager
- (instancetype)init
{
APIConfig *config = [[APIConfig alloc] init];
config.host = [XXAPIService shareInstance].currentHost;
config.commonParams = @{};
config.commonHeaderParams = @{};
return [self initWithConfig:config];
}
- (BOOL)willSendRequest:(APIRequest *)request
{
if (self. shouldEncrypt1)
{
//加密方式1...
}
return YES;
}
- (BOOL)willSuccessCallBack:(APIResponse *)response
{
NSDictionary *jsonObject = response.jsonObject;
if ([jsonObject isKindOfClass:[NSDictionary class]])
{
if ([jsonObject[@"code"] integerValue] == 100)
{
return YES;
}
else
{
response.message = jsonObject[@"message"];
return NO;
}
}
response.message = @"服务器数据结构异常,请稍后再试";
return NO;
}
@end
网络层应该具备的特点
- 解决当前出现的需求
- 方便以后扩展功能,扩展功能尽量不影响已存在的接口
- 引导他人继续维护开发