从自己刚开始做项目时,一直都是用AFNetworking进行网络请求,每次都会写一大坨代码,后来觉得每次都写这么多代码觉得很dan疼,然后自己就去搜索网络上对网络请求封装,到后来去了一家公司,接触到人家的封装,感觉自己的封装完全处于幼儿园水平,读过唐巧大大分享过的YTKNetwork,YTKNetwork的封装和设计很棒,但是不适合一些小项目。目前工作较少,结合自己的理解,分享一下自己关于封装一个网络请求工具的理解。(PS:出于对以前技术总监封装工具成果的尊重,就不改变他设计的类名和工具名称,主要说的是我对这个工具的理解和运用)
关于封装工具的设计
对于一个工具类的设计,个人觉得要分为三个部分,第一部分是工具的调用,第二部分是工具的参数处理,第三部分是工具的结果处理,将工具分为,调用、处理、和结果三个部分,创建三个类APIClient、APIRequest、APIResult。
首先对接口返回数据进行分析,找了一个接口的数据,删除了多余的属性,知道返回数据的结构才能进行APIResult的设计
{
Error = {
ID = 0;
Message = "<null>";
};
Result = {
Datas = (
{
"create_time" = "2016-05-31";
id = 421;
name = test;
"user_id" = 133;
"user_level" = "<null>";
"user_nike" = "\U77f3\U5bb6\U5e84\U4e2d\U5fc3\U8840\U7ad9";
}
);
TotalNum = 102;
};
}
我们后台接口返回数据格式是json格式,最外层是一个字典,字典里面有Error和Result俩个小字典,Error字典里面有ID和message俩个键值对,Result是里面是数据。
在下面的这个方法中
- (id)initWithDictionary:(NSDictionary *)dic
首先对处理的字典进行判断,判断字典是否存在:
如果字典存在,取出返回数据的状态码、接口返回提示信息与接口数据;
如果字典不存在,将message赋值@"网络错误",输出字典置nil,错误状态码没有收集就随便写了一个102;
如果接收到异常,把字典赋值给sr.dic, 错误状态码赋值0;
APiResult的布尔属性success根据错误状态码是否等于0进行自动赋值YES或者NO;
具体代码如下:
APIResult.h
#import <Foundation/Foundation.h>
@interface APIResult : NSObject
/** 提示信息 */
@property (nonatomic, copy) NSString *message;
/** 请求状态 */
@property (nonatomic, assign) NSInteger status;
/** 请求是否成功 */
@property (nonatomic, readonly) BOOL success;
/** 接收数据的字典 */
@property (nonatomic, strong) NSDictionary *dic;
/** 字典处理 */
- (id)initWithDictionary:(NSDictionary *)dic;
@end
#import "APIResult.h"
@implementation APIResult
- (BOOL)success
{
return self.status == 0;
}
- (id)initWithDictionary:(NSDictionary *)dic
{
if (self = [super init]) {
@try {
if (dic) {
// 取出返回数据的状态码
self.status = [[[dic objectForKey:@"Error"] objectForKey:@"ID"] intValue];
// 提示信息
self.message = [[dic objectForKey:@"Error"] objectForKey:@"Message"];
NSDictionary *data = [dic objectForKey:@"Result"];
// 返回数据
self.dic = data;
} else {
// 没有返回数据
self.message = @"网络错误";
self.dic = nil;
self.status = 102; // 暂时定义无效的网络
}
}
//接收到异常
@catch (NSException *exception) {
self.dic = dic;
self.status = 0;
}
@finally {
}
}
return self;
}
@end
知道接口返回数据的格式了并且已经把APIResult封装好了就可以开始处理请求参数的设置了,APIRequest主要有四个方法和三个代理方法:
APIRequest初始化时进行调用,传进代理和内部参数初始化
- (id)initWithDelegate:(id<APIRequestDelegate>)delegate;
拼接一些需要的公共参数,由于参数每次都要具体设置就没添加公共参数
- (void)appendBaseParams;
接口调用成功返回数据
- (void)callBackFinishedWithDictionary:(NSDictionary *)dic;
接口调用失败返回错误
- (void)callBackFailed:(NSError *)error;
代理方法:网络请求成功,接口调用成功返回数据
- (void)serverApi_FinishedSuccessed:(APIRequest *)api result:(APIResult *)sr;
代理方法:网络请求成功,接口调用失败未返回数据
- (void)serverApi_FinishedFailed:(APIRequest *)api result:(APIResult *)sr;
代理方法:网络请求失败,返回错误
- (void)serverApi_RequestFailed:(APIRequest *)api error:(NSError *)error;
在代理方法设计中,不仅将返回数据传递给代理,将APIRequest本身传递给代理,便于接口调试,在具体调用会进行解释,.h里面的属性相信大家一看就看的懂的,主要是参数进行拼接和默认值设置,就不具体的一一说明了。
详细代码如下
APIRequest.h
#import <Foundation/Foundation.h>
#import "APIResult.h"
@class APIRequest;
// 默认的网络请求的延时时间
#define defaultAPIRequestTimeOutSeconds 30
typedef enum ApiAccessType {
kApiAccessPost, // Post方式
kApiAccessUpload // 上传图片
}ApiAccessType;
@protocol APIRequestDelegate <NSObject>
@optional
- (void)serverApi_FinishedSuccessed:(APIRequest *)api result:(APIResult *)sr;
- (void)serverApi_RequestFailed:(APIRequest *)api error:(NSError *)error;
- (void)serverApi_FinishedFailed:(APIRequest *)api result:(APIResult *)sr;
@end
@interface APIRequest : NSObject
#pragma mark - 基本属性
/** 请求类型 */
@property (nonatomic, readonly) ApiAccessType accessType;
/** 请求返回的格式 */
@property (nonatomic, readonly) ApiResultFormat resultFormat;
/** 请求超时时间 */
@property (nonatomic, readonly) NSTimeInterval timeout;
/** 请求路径 */
@property (nonatomic, readonly) NSString *fullUrl;
/** 服务器地址 */
@property (nonatomic, readonly) NSString *serviceUrl;
/** 接口方法名 */
@property (nonatomic, readonly) NSString *urlAction;
/** 上传图片接口地址 */
@property (nonatomic, readonly) NSString *UrlUpload;
/** 上传图片image */
@property (nonatomic, strong) UIImage *uploadImage;
/** 代理 */
@property (nonatomic, weak) id<APIRequestDelegate> delegate;
/** 请求参数数组 */
@property (nonatomic, strong) NSMutableArray *params;
/** 上传图片参数字典 */
@property (nonatomic, strong) NSMutableDictionary *paramDict;
#pragma mark - 分页相关
@property (nonatomic, assign) NSInteger requestCurrentPage;// 当前请求页 分页从0开始
@property (nonatomic, assign) NSInteger requestMaxPage;// 最大请求页
#pragma mark - 基本方法
/** 初始化 */
- (id)initWithDelegate:(id<APIRequestDelegate>)delegate;
/** 拼接公共参数 */
- (void)appendBaseParams;
#pragma mark - APIRequestDelegate回调方法
/** 返回数据调用方法 */
- (void)callBackFinishedWithDictionary:(NSDictionary *)dic;
/** 返回数据错误 */
- (void)callBackFailed:(NSError *)error;
@end
APIRequest.m
#import "APIRequest.h"
@implementation APIRequest
// 初始化
- (id)initWithDelegate:(id<APIRequestDelegate>)delegate
{
if (self = [self init]) {
self.params = [NSMutableArray array];
self.paramDict = [NSMutableDictionary dictionary];
self.delegate = delegate;
self.requestCurrentPage = 0;
self.requestMaxPage = NSIntegerMax;
// [self appendBaseParams]; 暂时不需要拼接公共参数
}
return self;
}
// 默认的是Get方式进行访问
- (ApiAccessType)accessType
{
return kApiAccessPost;
}
// 默认的超时时间
- (NSTimeInterval)timeout
{
return defaultAPIRequestTimeOutSeconds;
}
// 默认服务器地址
- (NSString *)serviceUrl
{
return @"serviceUrl";
}
// 默认接口方法地址
- (NSString *)urlAction
{
return @"urlAction";
}
// 上传图片接口
- (NSString *)UrlUpload
{
return @"UploadFile";
}
// 拼接的请求地址
- (NSString *)fullUrl
{
NSString *url = [NSString stringWithFormat:@"%@%@", self.serviceUrl, self.urlAction];
return url;
}
// 数据请求完成的 回调
- (void)callBackFinishedWithDictionary:(NSDictionary *)dic
{
// 处理 responseObject
APIResult *sr = [[APIResult alloc] initWithDictionary:dic];
// error ID = 0 网络请求成功,接口调用成功返回数据
if (sr.success) {
if (self.delegate && [self.delegate respondsToSelector:@selector(serverApi_FinishedSuccessed:result:)]) {
[self.delegate serverApi_FinishedSuccessed:self result:sr];
}
// error ID = 1 网络请求成功,接口调用失败未返回数据
} else {
if (self.delegate && [self.delegate respondsToSelector:@selector(serverApi_FinishedFailed:result:)]) {
[self.delegate serverApi_FinishedFailed:self result:sr];
}
}
}
// 数据请求失败的回调
- (void)callBackFailed:(NSError *)error
{
if (self.delegate && [self.delegate respondsToSelector:@selector(serverApi_RequestFailed:error:)]) {
[self.delegate serverApi_RequestFailed:self error:error];
}
}
// 拼接的基本参数
- (void)appendBaseParams
{
}
@end
参数处理和结果处理OK了,下面代码就开始调用了APIClient进行接口数据请求了。
主要调用下面的这个方法、参数和接口地址都是网络请求之前配置好的,我们接口主要是post请求和上传单个图片、其他方法类似页可以添加进去,增加APIRequest的请求类型就好了,参数都在APIRequest的params里面,如果后台参数要求字典类型,同理可以进行替换更换
+ (void)execute:(APIRequest *)api
APIClient.h
#import <Foundation/Foundation.h>
@class APIRequest;
@interface APIClient : NSObject
/** APIClient 初始化 */
+ (APIClient *)sharedInstance;
/** 执行不同网络请求 */
+ (void)execute:(APIRequest *)api;
@end
APIClient.m
#import "APIClient.h"
#import "AFNetworking.h"
#import "APIRequest.h"
@implementation APIClient
// 采用单例方法创建对象
+ (APIClient *)sharedInstance
{
static APIClient *apiClient;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
apiClient = [[self alloc] init];
});
return apiClient;
}
/** 执行post网络请求 */
+ (void)executePostRequestWithApi:(APIRequest *)api
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
//网络请求超时时间
manager.requestSerializer.timeoutInterval = api.timeout;
manager.responseSerializer = [AFJSONResponseSerializer serializer];
//此项可以不设置
manager.securityPolicy.allowInvalidCertificates = YES;
//此项可以不设置
manager.securityPolicy.validatesDomainName = NO;
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html", nil];
//不需要进行请求头可以不设置
[manager.requestSerializer setValue:@"参数_value" forHTTPHeaderField:@"参数_key"];
[manager.requestSerializer setValue:@"参数_value" forHTTPHeaderField:@"参数_key"];
[manager POST:api.fullUrl parameters:api.params success:^(AFHTTPRequestOperation *operation, id responseObject){
NSDictionary *outDic = nil;
//进行字典类型转换
outDic = (NSDictionary *)responseObject;
[api callBackFinishedWithDictionary:outDic];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[api callBackFailed:error];
}];
}
// 上传单张图片
+ (void)executeUploadRequestWithApi:(APIRequest *)api
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer.timeoutInterval = api.timeout;
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager POST:api.UrlUpload parameters:api.paramDict constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
// fileName 名字可以随意配置 不过结尾得有 .jpg 或者 .png
NSString *fileName = [NSString stringWithFormat:@"%@%@%@%@",[api.paramDict objectForKey:@"user_id"], [api.paramDict objectForKey:@"business_type"],[api.paramDict objectForKey:@"business_id"],@".jpg"];
// 直接拼接的压缩的二进制图片数据
[formData appendPartWithFileData: UIImageJPEGRepresentation(api.uploadImage, 0.8) name:@"file" fileName:fileName mimeType:@"image/jpg"];
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSDictionary *outDic = nil;
outDic = (NSDictionary *)responseObject;
[api callBackFinishedWithDictionary:outDic];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[api callBackFailed:error];
}];
}
// 执行不同的网络请求
+ (void)execute:(APIRequest *)api
{
//HWLog(@"%@", api.fullUrl);
//HWLog(@"%@", api.params);
//根据 api的accessType枚举类型执行不同方法
switch (api.accessType)
{
case kApiAccessPost:
{
[APIClient executePostRequestWithApi:api];
break;
}
case kApiAccessUpload:
{
[APIClient executeUploadRequestWithApi:api];
break;
}
default:
break;
}
}
@end
下面是讲一下具体调用,首先新意见一个类ApiIRequestCircleList继承自APIRequest
#import "APIRequest.h"
@interface ApiIRequestCircleList : APIRequest
- (void)setGetCircleListParamsWithUserID:(NSString *)userID
circleName:(NSString *)circleName
page:(NSInteger )page;
@end
#import "ApiIRequestCircleList.h"
@implementation ApiIRequestCircleList
//重写父类的接口方法名
- (NSString *)urlAction
{
return @"接口方法名";
}
//对外提供设置参数方法
- (void)setGetCircleListParamsWithUserID:(NSString *)userID
circleName:(NSString *)circleName
page:(NSInteger )page;
{
[self.params addObject:userID];
[self.params addObject:circleName];
[self.params addObject:@(page)];
[self.params addObject:@(20)];
}
@end
在对应需要调用网络请求的Controller里面要遵守APIRequestDelegate,我习惯把请求类设置为一个属性使用懒加载、懒加载的好处都懂的哈
@property (nonatomic, strong) ApiIRequestCircleList *apiCircleList;
- (ApiIRequestCircleList *)apiCircleList
{
if (_apiCircleList == nil) {
_apiCircleList = [[ApiIRequestCircleList alloc] initWithDelegate:self];
}
return _apiCircleList;
}
//请出请求参数的数组、避免多个网络请求调用父类APIRequest的params属性数据重复,好多次参数错误告诉我加这句话是靠谱的
[self.apiCircleList.params removeAllObjects];
//添加需要的参数
[self.apiCircleList setGetCircleListParamsWithUserID:self.userID circleName:@"" page:0];
//最后工具执行网络请求
[APIClient execute:self.apiCircleList];
最后就是网络请求完成后的代理回调处理了
- (void)serverApi_FinishedSuccessed:(APIRequest *)api result:(APIResult *)sr
{
if (api == self.apiCircleList)
{
NSLog(@"%@",self.apiCircleList.fullUrl); //接口地址
NSLog(@"%@",self.apiCircleList.params); //请求参数
NSLog(@"%@",sr.dic); //返回数据 Result
}
if (api == self.apiJoinCircle)
{
}
}
- (void)serverApi_FinishedFailed:(APIRequest *)api result:(APIResult *)sr
{
NSLog(@"%@", sr.message];
}
- (void)serverApi_RequestFailed:(APIRequest *)api error:(NSError *)error
{
HWLog(@"%@", error);
}
看着上面的三个代理函数是不是很清爽,网络请求成功返回数据、网络请求成功为返回数据(参数错误主要都执行这个方法),网络请求失败,在网络请求成功返回数据的代理中,如果页面有多个请求,可以对api进行判断区分不同的网络请求,sr.dic就是借口返回的字典里面Result里面的内容,如果想提示接口返回的message,sr.message就是你需要的信息了,最开始的时候提到代理把APIRequest传递过来是为了调试,如果请求不成功,可以在代理的方法中直接打印参数和接口地址。还要一直都在用这个封装的原因是不喜欢把超级多的代码放在AFN的成功和失败的block里面,这样的代理方法设置清晰明了,而且失败的提示信息可以统一用MBProgressHUD进行提示,把提示信息show出来,我的理解和代码都在这里了,还在爬坑,有不对的地方请多指教。
最后demo地址,欢迎大家下载,最好给个star😁