基于AFNetworking封装网络请求

从自己刚开始做项目时,一直都是用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😁

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,498评论 25 707
  • AFHTTPRequestOperationManager 网络传输协议UDP、TCP、Http、Socket、X...
    Carden阅读 4,319评论 0 12
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,022评论 4 62
  • 日本儿童文学家柳田邦男说:"人的一辈子有三次读童书的机会,第一次是自己是孩子的时候,第二次是自己抚养孩子的时...
    Joanne_fa80阅读 257评论 2 4
  • 那是我上高二那年,我依旧像曾经一样不学无术,各种玩闹。上课和好朋友讲话,打架,挠痒痒,然而没多久我和朋友便被老师分...
    落魄失魂阅读 265评论 0 0