基于AFNetworking封装网络请求

从自己刚开始做项目时,一直都是用AFNetworking进行网络请求,每次都会写一大坨代码,后来觉得每次都写这么多代码觉得很dan疼,然后自己就去搜索网络上对网络请求封装,到后来去了一家公司,接触到人家的封装,感觉自己的封装完全处于幼儿园水平,读过唐巧大大分享过的YTKNetwork,YTKNetwork的封装和设计很棒,但是不适合一些小项目。目前工作较少,结合自己的理解,分享一下自己关于封装一个网络请求工具的理解。(PS:出于对以前技术总监封装工具成果的尊重,就不改变他设计的类名和工具名称,主要说的是我对这个工具的理解和运用)

关于封装工具的设计

对于一个工具类的设计,个人觉得要分为三个部分,第一部分是工具的调用,第二部分是工具的参数处理,第三部分是工具的结果处理,将工具分为,调用、处理、和结果三个部分,创建三个类APIClient、APIRequest、APIResult。

首先对接口返回数据进行分析,找了一个接口的数据,删除了多余的属性,知道返回数据的结构才能进行APIResult的设计

{
    Error =     {
        ID = 0;
        Message = "";
    };
    Result =     {
        Datas =         (
                         {
                             "create_time" = "2016-05-31";
                             id = 421;
                             name = test;
                             "user_id" = 133;
                             "user_level" = "";
                             "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 
@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)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 
#import "APIResult.h"
@class APIRequest;
//  默认的网络请求的延时时间
#define defaultAPIRequestTimeOutSeconds     30
typedef enum ApiAccessType {
    kApiAccessPost,                  // Post方式
    kApiAccessUpload                 // 上传图片
}ApiAccessType;
@protocol APIRequestDelegate 
@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 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)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)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 
@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 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

你可能感兴趣的:(基于AFNetworking封装网络请求)