基于AFNetworking封装网络请求

作者: 咖啡bu加糖 | 来源:发表于2016-06-17 14:13 被阅读3629次

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

    相关文章

      网友评论

      • 天城一哥:同求demo,谢谢,哥们,1475117940@qq.com
        咖啡bu加糖:@天城一哥 有链接仔细看看
      • ___Twotigers:楼主求Demo 373983214@qq.com
      • ChefZhang:不过代理方法写成block感觉会更方便一点
        咖啡bu加糖:为了分的清楚,不像block堆在一起才用的代理,看个人喜好吧
      • ChefZhang:很不错哦,能给一个demo吗?755757083@qq.com
        咖啡bu加糖:@ChefZhang github链接直接下载就行啊
      • Hiram_ios:xhljob@126.com 求demo啊
        咖啡bu加糖:@___Twotigers 不是有链接么
        ___Twotigers:楼主求Demo 373983214@qq.com
        咖啡bu加糖:@Hiram_ios 看github
      • KuKuMan:楼主能发个demo吗zhaoqiweiV@163.com,灰常感谢,已关注,期待你的好作品
        咖啡bu加糖:哥们你加下群
      • 吃屁的小栗子:同求demo一份 469111272@qq.com 好人一生平安😂 感谢!!
        咖啡bu加糖:539641834
        咖啡bu加糖:@吃屁的小栗子 加群自动获取
      • GaryHuang:楼主有空发我份demo,1351232396@qq.com,谢谢!!
        GaryHuang:@李大宝是个小胖子 好的 已加
        咖啡bu加糖:加群自己下载更方便哈
      • 8a2694faac8d:楼主有空发我份demo,1056412923@qq.com,谢谢!!
        咖啡bu加糖:@8a2694faac8d 看文章结尾你就知道哪里获取了
      • 9aaeb2bf34a3:求demo,554979047@qq.com,谢谢~
        咖啡bu加糖:@9aaeb2bf34a3 末尾有群号你加一下,群文件里有demo
      • 苏丶小洛:同求一份demo研究下,704556640@qq.com,楼主好人...
        苏丶小洛:@李大宝是个小胖子 已收到,谢谢了!
        咖啡bu加糖:@苏丶小洛 已发送到你邮箱
        咖啡bu加糖:@苏丶小洛 等下午休结束给你发,现在宿舍休息
      • 致命前奏:最近就在搞AFN封装,看了你这个感觉我的太Low了,实在想学习下,方便的话可以发个代码吗?十分感谢您的文章! findhope.china@gmail.com
        咖啡bu加糖:@致命前奏 用得爽就好
        致命前奏: @李大宝是个小胖子 十分感谢!
        咖啡bu加糖:@致命前奏 已发邮箱。
      • 洋之_:哥们,写的不错,分享下吧3224479663@qq.com
        洋之_:思路简单清晰,结构明了,一级棒👍👍!
        咖啡bu加糖:@洋之_ 已发送到邮箱
        咖啡bu加糖:@洋之_ 晚点发个demo给你和之前的一个哥们,之前忙飞了
      • kinmo:来个demo loveJuly@vip.163.com
        咖啡bu加糖:@July丶ye 就简单的例子,有问题在找我
        kinmo:@咖啡bu加糖 好的👌🏻谢谢
        咖啡bu加糖:@July丶ye 已发送到邮箱
      • King圣:有2个问题:requeset.h里的resultFormat请求返回属性没有用2、ApiRequestCircleList里面不是应该重写fullUrl或者服务器名,方法里面写接口名嘛
        咖啡bu加糖:@King圣 因为有个是接口返回数据格式的监测,这个没有做,所以就没用到这个属性
      • King圣:楼主好人,605188105@qq.com
        咖啡bu加糖:@King圣 前这一阵子比较忙,一直没看简书,已经发给你邮箱了,不好意思哈
      • 小的小碰撞:求Demo 兄弟 915954109@qq.com
        咖啡bu加糖:@licl19 明天加群 群文件有
        流年易逝_李:@李大宝是个小胖子 同求demo,谢谢,1365343119@qq.com
        咖啡bu加糖:@小的小碰撞 前这一阵子比较忙,一直没看简书,已经发给你邮箱了,不好意思哈
      • 咖啡bu加糖:给我个邮箱吧,回头发给你
      • 凡尘一笑:哥们给个Dome看下

      本文标题:基于AFNetworking封装网络请求

      本文链接:https://www.haomeiwen.com/subject/uwdvdttx.html