iOS重构-轻量级的网络请求封装实践

前言

十分钟搭建主流框架_简单的网络部分(OC)
中,我们使用AFN框架顺利的发送网络请求并返回了有用数据,但对AFN框架的依赖十分严重,下面我们重构一下。

源码github地址

初步

  • 很多时候,我们涉及到网络请求这块,都离不开几个第三方框架,AFNetworkingMJExtention, MBProgressHUD(SV)
  • 初学的时候,都会把它们写到Controller里面,如下:
        [[AFHTTPSessionManager manager] GET:CYXRequestURL parameters:params success:^(NSURLSessionDataTask * _Nonnull task, id  _Nonnull responseObject) {
            NSLog(@"请求成功");

            // 利用MJExtension框架进行字典转模型
            weakSelf.menus = [CYXMenu objectArrayWithKeyValuesArray:responseObject[@"result"]];

            // 刷新数据(若不刷新数据会显示不出)
            [weakSelf.tableView reloadData];

        } failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) {
            NSLog(@"请求失败 原因:%@",error);
        }];
  • 这样会造成耦合性过高的问题,灵活性也非常不好,因此,AFN的作者也推荐我们不要直接使用,新建一个网络请求类来继承AFN的使用方式更好。

  • 因此,继承的方式,如下:

    • CYXHTTPSessionManager.h文件

      #import <AFHTTPSessionManager.h>
      @interface CYXHTTPSessionManager : AFHTTPSessionManager
      @end
      
    • CYXHTTPSessionManager.m文件

      #import "CYXHTTPSessionManager.h"
      @implementation CYXHTTPSessionManager
      + (instancetype)manager{
          CYXHTTPSessionManager *mgr = [super manager];
          //    这里可以做一些统一的配置
          //    mgr.responseSerializer = ;
          //    mgr.requestSerializer = ;
          return mgr;
      }
      @end
      
  • 调用方式:

/** 请求管理者 */
@property (nonatomic,weak) CYXHTTPSessionManager * manager;

    // 发送请求
    [self.manager GET:CYXRequestURL parameters:params success:^(NSURLSessionDataTask * _Nonnull task, id  _Nonnull responseObject) {
        // 存储 maxtime
        weakSelf.maxtime = responseObject[@"info"][@"maxtime"];
        
        weakSelf.topics =  [CYXTopic objectArrayWithKeyValuesArray:responseObject[@"list"]];
        CYXLog(@"%@",responseObject[@"list"]);
        [weakSelf.tableView reloadData];
        
        // 结束刷新
        [weakSelf.tableView.header endRefreshing];
        
    } failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) {
        [weakSelf.tableView.header endRefreshing];
    }];
  • 这样,已经降低了一点耦合度,也不需要在每个需要发送网络请求的Controller中引入AFN框架了。但对于MJExtension框架的依赖还是没有改善。

进阶

  • 通过观察,我们发现其实大部分的GET和POST请求的前几步基本使用步骤是大致相同的,相同的步骤如下:

    • 1.通过AFN请求回来JSON数据
    • 2.通过JSON数据,取出需要使用的字典数组/字典
    • 3.使用字典转模型框架(MJExtension)把字典数组转化为模型数组/字典转化为模型
  • 因此,我们思考能不能把这些相同的步骤封装起来,以后就不需要重复写这些代码了,我们都知道一条经典的编程法则:“Don't repeat youself”。这就是我们封装与重构的理由!


1.基层请求的封装

  • 本文示例封装POST请求
  • CYXHttpRequest.h文件
#import <Foundation/Foundation.h>
#import "AFNetworking.h"

@interface CYXHttpRequest : NSObject

/**
 *  发送一个POST请求
 *
 *  @param url     请求路径
 *  @param params  请求参数
 *  @param success 请求成功后的回调
 *  @param failure 请求失败后的回调
 */
+ (void)post:(NSString *)url params:(NSDictionary *)params success:(void (^)(id responseObj))success failure:(void (^)(NSError *error))failure;

@end
  • CYXHttpRequest.m文件
#import "CYXHttpRequest.h"
@implementation CYXHttpRequest
+ (void)post:(NSString *)url params:(NSDictionary *)params success:(void (^)(id))success failure:(void (^)(NSError *))failure
{
    // 1.获得请求管理者
    AFHTTPSessionManager *mgr = [AFHTTPSessionManager manager];
    // 2.申明返回的结果是text/html类型
    mgr.responseSerializer = [AFHTTPResponseSerializer serializer];
    // 3.设置超时时间为10s
    mgr.requestSerializer.timeoutInterval = 10;
    // 4.发送POST请求
    [mgr POST:url parameters:params progress:^(NSProgress * _Nonnull uploadProgress) {
        
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        if (success) {
            success(responseObject);
        }
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        if (failure) {
            failure(error);
        }
    }];
}

@end
  • 现在已经可以把网络数据请求回来了,轮到第二个步骤了:观察请求回来的JSON数据,取出需要使用的字典数组/字典。在这里再作一层封装。举个简单的例子,假如返回的JSON数据结构如下:
{
    "error_code": 0,
    "reason": "Success",
    "result": [{
        "id": 370622,
        "title": "西红柿蒜薹炒鸡蛋",
        "tags": "厨房用具;厨具;加工工艺;基本工艺;菜品;菜肴;家常菜;炒;炒锅;热菜;防辐射;开胃;蔬菜类;果实类;蒜薹;西红柿;禽蛋类;蛋;鸡蛋;",
        "intro": "我这的蒜薹鸡蛋都爱加西红柿、辣椒一起炒的,这是习惯所致,爱吃西红柿,爱吃辣椒,还爱把菜搭配的颜色亮丽,当然味道也不差。",
        "ingredients": "西红柿:1个;蒜薹:200g;鸡蛋:2个;",
        "burden": "油:适量;盐:适量;青辣椒:1个;红辣椒:1个;",
        "albums": "http://imgs.haoservice.com/CaiPu/pic/recipe/l/be/a7/370622_86e12b.jpg",
        }
       {
        "id": 433079,
        "title": "西红柿酸奶",
        "tags": "促进食欲;减肥;懒人食谱;消暑食谱;美容养颜;",
        "intro": "新疆人爱吃西红柿那是有目共睹的,菜里面加西红柿的数不胜数,就连舌尖2在吐鲁番拍的葡萄干抓饭里面都加西红柿。",
        "ingredients": "酸奶:400g;西红柿:200g;",
        "burden": "白糖:20g;",
        "albums": "http://imgs.haoservice.com/CaiPu/pic/recipe/l/b7/9b/433079_377373.jpg",
       }
       {···}]
}

2.简单业务逻辑封装

  • 现在只需要使用到result数据(并对应CYXMenu模型),在公司中,接口一般会有比较好的规范,即每个接口的模型属性一般都有统一的命名。
  • 我们使用时,通常会把result字典数组转化成CYXMenu模型数组。因此,可以进一步的封装出CYXBaseRequest对象。
  • CYXBaseRequest类实现思路如下:
    • 1.使用CYXHttpRequest发起网络请求,返回数据中取到result
    • 2.使用MJExtensionresult字典数组转化成CYXMenu模型数组,并返回模型数组
    • 3.外界只需要传递进来一个resultClass即可。
  • CYXBaseRequest实现代码如下:
  • CYXBaseRequest.h文件
#import <Foundation/Foundation.h>

@interface CYXBaseRequest : NSObject

/**
 *  返回result 数据模型
 *
 *  @param url          请求地址
 *  @param param        请求参数
 *  @param resultClass  需要转换返回的数据模型
 *  @param success      请求成功后的回调
 *  @param warn         请求失败后警告提示语
 *  @param failure      请求失败后的回调
 *  @param tokenInvalid token过期后的回调
 */
+ (void)postResultWithUrl:(NSString *)url param:(id)param
              resultClass:(Class)resultClass
                  success:(void (^)(id result))success
                     warn:(void (^)(NSString *warnMsg))warn
                  failure:(void (^)(NSError *error))failure
             tokenInvalid:(void (^)())tokenInvalid;

/**
 *  返回result 数据模型(带HUD)
 *
 *  @param url          请求地址
 *  @param param        请求参数
 *  @param resultClass  需要转换返回的数据模型
 *  @param success      请求成功后的回调
 *  @param warn         请求失败后警告提示语
 *  @param failure      请求失败后的回调
 *  @param tokenInvalid token过期后的回调
 */
+ (void)postResultHUDWithUrl:(NSString *)url param:(id)param
                 resultClass:(Class)resultClass
                     success:(void (^)(id result))success
                        warn:(void (^)(NSString *warnMsg))warn
                     failure:(void (^)(NSError *error))failure
                tokenInvalid:(void (^)())tokenInvalid;

/**
 *  组合请求参数
 *
 *  @param dict 外部参数字典
 *
 *  @return 返回组合参数
 */
+ (NSMutableDictionary *)requestParams:(NSDictionary *)dict;

@end

  • CYXBaseRequest.m文件
#import "CYXBaseRequest.h"
#import "CYXHttpRequest.h"
#import "ExceptionMsgTips.h"
#import "MJExtension.h"

@implementation BSBaseRequest

/**
 *  返回result 数据模型(HUD)
 */
+ (void)postResultHUDWithUrl:(NSString *)url param:(id)param
                 resultClass:(Class)resultClass
                     success:(void (^)(id result))success
                        warn:(void (^)(NSString *warnMsg))warn
                     failure:(void (^)(NSError *error))failure
                tokenInvalid:(void (^)())tokenInvalid
{
    
    [self postBaseHUDWithUrl:url param:param resultClass:resultClass
                     success:^(id responseObj) {
                         if (!resultClass) {
                             success(nil);
                             return;
                         }
                         success([resultClass mj_objectArrayWithKeyValuesArray:responseObj[@"result"]]);
                     }
                        warn:warn
                     failure:failure
                tokenInvalid:tokenInvalid];
}

/**
 *  返回result 数据模型
 */
+ (void)postResultWithUrl:(NSString *)url param:(id)param
              resultClass:(Class)resultClass
                  success:(void (^)(id result))success
                     warn:(void (^)(NSString *warnMsg))warn
                  failure:(void (^)(NSError *error))failure
             tokenInvalid:(void (^)())tokenInvalid
{
    
    [self postBaseWithUrl:url param:param resultClass:resultClass
                  success:^(id responseObj) {
                      if (!resultClass) {
                          success(nil);
                          return;
                      }
                      success([resultClass mj_objectArrayWithKeyValuesArray:responseObj[@"result"]]);
                  }
                     warn:warn
                  failure:failure
             tokenInvalid:tokenInvalid];
}

/**
 *  数据模型基类方法
 */
+ (void)postBaseWithUrl:(NSString *)url param:(id)param
            resultClass:(Class)resultClass
                success:(void (^)(id result))success
                   warn:(void (^)(NSString *warnMsg))warn
                failure:(void (^)(NSError *error))failure
           tokenInvalid:(void (^)())tokenInvalid
{
//    url = [NSString stringWithFormat:@"%@%@",Host,url];
    CYXLog(@"\\n请求链接地址---> %@",url);
    //状态栏菊花
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
    [CYXHttpRequest post:url params:param success:^(id responseObj) {
        if (success) {
            NSDictionary *dictData = [NSJSONSerialization JSONObjectWithData:responseObj options:kNilOptions error:nil];
            CYXLog(@"请求成功,返回数据 : %@",dictData);
            success(dictData);
        }
        //状态栏菊花
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    } failure:^(NSError *error) {
        if (failure) {
            failure(error);
            CYXLog(@"请求失败:%@",error);
        }
        //状态栏菊花
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    }];
}
/**
 *  数据模型基类(带HUD)
 */
+ (void)postBaseHUDWithUrl:(NSString *)url param:(id)param
               resultClass:(Class)resultClass
                   success:(void (^)(id result))success
                      warn:(void (^)(NSString *warnMsg))warn
                   failure:(void (^)(NSError *error))failure
              tokenInvalid:(void (^)())tokenInvalid
{
    [SVProgressHUD showWithStatus:@""];
    [self postBaseWithUrl:url param:param resultClass:resultClass success:^(id responseObj) {
        [SVProgressHUD dismiss];    //隐藏loading
        success(responseObj);
    } warn:^(NSString *warnMsg) {
        [SVProgressHUD dismiss];
        warn(warnMsg);
    } failure:^(NSError *fail) {
        [SVProgressHUD dismiss];
        failure(fail);
    } tokenInvalid:^{
        [SVProgressHUD dismiss];
        tokenInvalid();
    }];
}
@end
  • 到这里,轻量级的封装介绍已经全部介绍完了,更多的功能封装有待读者自己去研究了。既然封装好了,下面我们来介绍一下如何使用,其实非常简单。

使用介绍

  • 1.把上述两个类的.h .m 文化拖到您项目中,最好新建一个<Request>文件夹。
  • 2.在需要发送请求的Controller中#import "CYXBaseRequest.h"
  • 3.发送请求方法中的代码如下:
    • (使用CYXBaseRequest):
#pragma mark - 请求数据方法
- (void)loadData{
    self.pn = 1;
    // 请求参数(根据接口文档编写)
    NSMutableDictionary *params = [NSMutableDictionary dictionary];
    params[@"menu"] = @"西红柿";
    params[@"pn"] = @(self.pn);
    params[@"rn"] = @"10";
    params[@"key"] = @"fcfdb87c50c1485e9e7fa9f839c4b1a8";
    [CYXBaseRequest postResultWithUrl:CYXRequestURL param:params resultClass:[CYXMenu class] success:^(id result) {
        CYXLog(@"请求成功,返回数据 : %@",result);
        self.menus = result;
        self.pn ++;
        // 刷新数据(若不刷新数据会显示不出)
        [self.tableView reloadData];
        [self.tableView.mj_header endRefreshing];
    } warn:^(NSString *warnMsg) {
        
    } failure:^(NSError *error) {
        CYXLog(@"请求失败 原因:%@",error);
        [self.tableView.mj_header endRefreshing];
    } tokenInvalid:^{
        // 有登录操作的业务,这里返回登录状态
    }];
}
  • 在这里对比一下不使用CYXBaseRequest的发送请求方法代码:
#pragma mark - 请求数据方法
- (void)loadData{
    self.pn = 1;
    // 请求参数(根据接口文档编写)
    NSMutableDictionary *params = [NSMutableDictionary dictionary];
    params[@"menu"] = @"西红柿";
    params[@"pn"] = @(self.pn);
    params[@"rn"] = @"10";
    params[@"key"] = @"fcfdb87c50c1485e9e7fa9f839c4b1a8";    
    [self.manager.tasks makeObjectsPerformSelector:@selector(cancel)];
    [self.manager.responseSerializer setAcceptableContentTypes:[NSSet setWithObject:@"text/html"]];
    [self.manager POST:CYXRequestURL parameters:params progress:^(NSProgress * _Nonnull downloadProgress) {

    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        CYXLog(@"请求成功,返回数据 : %@",responseObject);
        // 利用MJExtension框架进行字典转模型
        weakSelf.menus = [CYXMenu  mj_objectArrayWithKeyValuesArray:responseObject[@"result"]];
        weakSelf.pn ++;
        // 刷新数据(若不刷新数据会显示不出)
        [weakSelf.tableView reloadData];
        [weakSelf.tableView.mj_header endRefreshing];
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        CYXLog(@"请求失败 原因:%@",error);
        [weakSelf.tableView.mj_header endRefreshing];
    }];
}

  • 虽然从代码看似两种使用差别不太大(只是少了几行代码),但相比之下,前者确实降低了对AFN等框架的依赖,并省去了每次都手动转一下模型的烦恼,现在你只需要把resultClass传过去,返回的数据便是已经转化好的模型,并在CYXBaseRequest内打印出请求链接地址返回数据等有用信息,方便调试,接口设计也类似AFN,使用简便。

  • TIPS:建议使用者可以在每个模块都建立Request文件(继承CYXBaseRequest),统一进行网络请求,这样更方便管理。

注:

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

推荐阅读更多精彩内容

  • AFHTTPRequestOperationManager 网络传输协议UDP、TCP、Http、Socket、X...
    Carden阅读 4,319评论 0 12
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,409评论 25 707
  • 公司来了一位新人,之前没有做过销售,领导让他跟着我学习。 开完早会,走出办公室,我直接就问了起来。 我:“你这个短...
    道心_3433阅读 232评论 0 0
  • 你没有发现吗?我们再也不是那两个可以彻夜电话的情侣,我们彼此越来越多的是陌生,我们没有共同的生活圈,也越来越...
    想飞的白兔子阅读 423评论 0 0