为什么要对AFNetworking进行多一层的封装
对于AFNetworking进行二次封装是很有必要的。事实上,在项目中大量使用第三方网络库是有风险的,因为网络请求的使用遍布整个应用,而一旦该对应的网络库不再更新维护,或者我们有意愿的去更换网络框架,修改将会有着巨大的难以承受的工作量。
这一个框架与其他框架有什么不同
本框架基于AFNetworking3.0的版本进行封装,面向更新的版本。
为网络请求的任务管理做了大量的工作,使得下载上传,或者其他使用环境下的任务管理得以更轻松的实现。
反其道而行,专为Post请求作出了缓存处理,这一部分缓存处理的使用与否,由使用者自行决定。
自定义Get请求的缓存策略,以时间为基准,严格把控缓存的有效性。
接口设计
请求
这里使用了新建NS_ENUM的方式来统一Post和Get接口。
/**
* 请求方式
*/
typedef NS_ENUM(NSInteger, CBRequestType) {
/**
* POST方式来进行请求
*/
CBPOSTRequest = 1 << 0,
/**
* GET方式来进行请求
*/
CBGETRequest = 1 << 1
};
/**
* 统一请求接口
*
* @param url 请求路径
* @param params 拼接参数
* @param httpMethod 请求方式(0为POST,1为GET)
* @param useCache 是否使用缓存
* @param progressBlock 进度回调
* @param successBlock 成功回调block
* @param failBlock 失败回调block
*
* @return 返回的对象中可取消请求
*/
+ (CBURLSessionTask *)requestWithUrl:(NSString *)url
params:(NSDictionary *)params
useCache:(BOOL)useCache
httpMedthod:(CBRequestType)httpMethod
progressBlock:(CBNetWorkingProgress)progressBlock
successBlock:(CBResponseSuccessBlock)successBlock
failBlock:(CBResponseFailBlock)failBlock;
从上面的代码我们可以看到,参数useCache决定着你是否使用Post请求的缓存功能,而即便将其设置为NO,依然会自动开启Get请求的缓存功能。
数据解析方式
/**
* 数据串行方式
*/
typedef NS_ENUM(NSInteger, CBSerializerType) {
/**
* HTTP方式来进行请求或响应
*/
CBHTTPSerializer = 1 << 0,
/**
* JSON方式来进行请求或响应
*/
CBJSONSerializer = 1 << 1
};
/**
* 更新请求或者返回数据的解析方式(0为HTTP模式,1为JSON模式)
*
* @param requestType 请求数据解析方式
* @param responseType 返回数据解析方式
*/
+ (void)updateRequestSerializerType:(CBSerializerType)requestType
responseSerializer:(CBSerializerType)responseType;
上面的NS_ENUM联合上面的方法可以更改数据解析方式,更具灵活性。
文件上传,下载
/**
* 文件上传接口
*
* @param url 传文件接口地址
* @param uploadingFile 上传文件路径
* @param progressBlock 上传进度
* @param successBlock 成功回调
* @param failBlock 失败回调
*
* @return 返回的对象中可取消请求
*/
+ (CBURLSessionTask *)uploadFileWithUrl:(NSString *)url
uploadingFile:(NSString *)uploadingFile
progressBlock:(CBNetWorkingProgress)progressBlock
successBlock:(CBResponseSuccessBlock)successBlock
failBlock:(CBResponseFailBlock)failBlock;
/**
* 文件下载接口
*
* @param url 下载文件接口地址
* @param saveToPath 存储目录
* @param progressBlock 下载进度
* @param successBlock 成功回调
* @param failBlock 下载回调
*
* @return 返回的对象可取消请求
*/
+ (CBURLSessionTask *)downloadWithUrl:(NSString *)url
saveToPath:(NSString *)saveToPath
progressBlock:(CBNetWorkingProgress)progressBlock
successBlock:(CBResponseSuccessBlock)successBlock
failBlock:(CBResponseFailBlock)failBlock;
针对文件的下载和上传的做出专门的设计。
任务管理
由上面的方法中,我们可以看到每一个方法都返回了一个任务对象CBURLSessionTask,这个对象继承于NSURLSessionTask,如此我们可以直接的取到每次进行任务请求的对象,直接对其进行管理。但不止于此,我仍然写了以下的几个接口来更快捷更集中的对其进行管理。
/**
* 取消所有请求
*/
+ (void)cancelAllRequest;
/**
* 根据url取消请求
*
* @param url 请求url
*/
+ (void)cancelRequestWithURL:(NSString *)url;
通过这两个方法,我们可以直接取消所有的请求,或者取消单个链接对应的请求。全局性的执行取消操作,更方便。
缓存处理
Post缓存处理
由于苹果官方的NSURLCache不支持Post请求的缓存处理,所有这一部分的缓存处理,我自己通过归档的方式来进行管理。
主要的方法如下:
+ (id)getCacheResponseWithURL:(NSString *)url
params:(NSDictionary *)params {
id cacheData = nil;
if (url) {
NSString *directoryPath = DIRECTORYPATH;
NSString *originString = [NSString stringWithFormat:@"%@+%@",url,params];
NSString *path = [directoryPath stringByAppendingPathComponent:[self md5:originString]];
NSData *data = [[NSFileManager defaultManager] contentsAtPath:path];
if (data) {
cacheData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
}
}
return cacheData;
}
+ (void)cacheResponseObject:(id)responseObject
request:(NSURLRequest *)request
params:(NSDictionary *)params {
if (request && responseObject && ![responseObject isKindOfClass:[NSNull class]]) {
NSString *directoryPath = DIRECTORYPATH;
NSError *error = nil;
if (![[NSFileManager defaultManager] fileExistsAtPath:directoryPath isDirectory:nil]) {
[[NSFileManager defaultManager] createDirectoryAtPath:directoryPath
withIntermediateDirectories:YES
attributes:nil
error:&error];
}
NSString *originString = [NSString stringWithFormat:@"%@+%@",request.URL.absoluteString,params];
NSString *path = [directoryPath stringByAppendingPathComponent:[self md5:originString]];
NSDictionary *dict = (NSDictionary *)responseObject;
NSData *data = nil;
if ([dict isKindOfClass:[NSData class]]) {
data = responseObject;
} else {
data = [NSJSONSerialization dataWithJSONObject:dict
options:NSJSONWritingPrettyPrinted
error:&error];
}
if (data && error == nil) {
[[NSFileManager defaultManager] createFileAtPath:path contents:data attributes:nil];
}
}
}
+ (NSString *)md5:(NSString *)string {
if (string == nil || [string length] == 0) {
return nil;
}
unsigned char digest[CC_MD5_DIGEST_LENGTH], i;
CC_MD5([string UTF8String], (int)[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding], digest);
NSMutableString *ms = [NSMutableString string];
for (i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
[ms appendFormat:@"%02x", (int)(digest[i])];
}
return [ms copy];
}
值得注意的是,每一次进行缓存,都通过HASH的方式对缓存进行了加密,从而达到唯一缓存和更加安全的目的。
Get请求的自定义
按道理,官方已经对于Get请求进行了很完善的缓存处理,为什么我还要自行处理呢?事实上,我并没有自定义这一部分的处理,我仅对于NSURLCache加多了一层封装,从而达到自定义策略的目的,主要的方法如下所示:
- (id)cachedResponseForRequest:(NSURLRequest *)request {
NSCachedURLResponse *cachedResponse = [super cachedResponseForRequest:request];
if (cachedResponse) {
NSDate *cacheDate = cachedResponse.userInfo[CBURLCacheExpirationKey];
NSDate *cacheExpirationDate = [cacheDate dateByAddingTimeInterval:CBURLCacheExpirationInterval];
if ([cacheExpirationDate compare:[NSDate date]] == NSOrderedAscending) {
[self removeCachedResponseForRequest:request];
return nil;
}
}
id responseObj = [NSJSONSerialization JSONObjectWithData:cachedResponse.data options:NSJSONReadingAllowFragments error:nil];
return responseObj;
}
- (void)storeCachedResponse:(id)response
responseObjc:(id)responseObjc
forRequest:(NSURLRequest *)request {
NSData *data = [NSJSONSerialization dataWithJSONObject:responseObjc options:NSJSONWritingPrettyPrinted error:nil];
NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] init];
userInfo[CBURLCacheExpirationKey] = [NSDate date];
NSCachedURLResponse *modifiedCachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data userInfo:userInfo storagePolicy:0];
[super storeCachedResponse:modifiedCachedResponse forRequest:request];
}