HZYNetwork 1.0.0

HZYNetwork 1.0.0

Michael Hao 维护。



  • 作者
  • 郝振壹

HZYNetwork

结构简述

类之间的关系

类关系图

工作流程

  1. 通过子类化 HZYRequest 设置请求的基本参数(url,parameter等);
  2. 执行 - (void)start; 方法;
    1. 使用 configurator 和基本参数构造一个 HZYNetworkTask 对象,该对象将 NSURLRequest 和完成回调以及进度回调封装起来;
    2. HZYNetworkTask 对象交给 HZYNetworkManager 单例;
      1. HZYNetworkTask 对象构造一个 HZYNetworkTaskOperation
      2. HZYNetworkTaskOperation 对象添加到 HZYNetworkTaskOperationQueue 队列中;
  3. 通过 delegate 方法或 block 获得响应。

快速上手

实现一个 HZYDemoURLRequest 类如下:

@interface HZYDemoURLRequest : HZYRequest
@end

@implementation HZYDemoURLRequest
- (NSString *)url {
    return @"http://www.mocky.io/v2/5ad857e83000003600e5872e";
}
@end

在要发起请求的地方执行如下方法

[[HZYDemoURLRequest requstUsingGETMethod] startWithSuccess:^(HZYRequest * _Nonnull request) {
        // success
} failure:^(HZYRequest * _Nonnull request) {
        // fail
}];

设计思路

在设计 HZYNetwork 时,我主要参考了 Casa Taloyum(以下简称 CT)大神的 iOS应用架构谈 网络层设计方案,以及猿题库(以下简称 YTK)的YTKNetwork,并结合自己对网络层的理解进行了实现。在阐述 HZYNetwork 的设计思路之前,先来看看下面三个问题:AFNetworking 做了什么、AFNetworking 缺少什么、HZYNetwork 要提供什么。

1.AFNetworking 做了什么?

AFN 对 URLSession 进行了封装,集中管理 delegate,提供便利的 URLSessionTask 构造方法;对外将 HTTP 请求再次简化,使用时只需要考虑 url、参数以及选择对应的 HTTP 方法。

2.AFNetworking 缺少什么
  • 发起请求是调用管理对象的方法,使得网络请求与控制器/视图高度耦合
  • 每一个请求变成了一次方法调用,对每个请求的具体区别处理变得困难;
  • 难以对已发出的请求进行批量操作;
  • 没有模块化的入参出参校验;
  • 没有办法管理多个请求之间的关系;
  • 存在多条业务线时,无法提供业务线级别的针对性管理;
3.HZYNetwork 要提供什么
  • 将请求独立封装,与请求相关的参数、设置等均集中在请求类中,减少控制器代码,增强代码可读性;
  • 对象化请求,满足各种对请求的自定义需求;
  • 提供 delegate 和 block 两种响应回调方式,增加编码的自由度,减少类间数据传递。
  • 对请求提供批量管理:取消、暂停、恢复;并发发出、串行发出、请求依赖等;
  • 对入参出参的验证,将数据验证作为模块与业务解耦并可实现复用;
  • 区分业务线,分别管理。如缓存策略、超时时间、额外的通用入参、HTTP Header 等;

对象化的请求,面向协议的接口

这同样是 CT 和 YTK 不约而同所做的事情。对比二者的实现,在设计 HZYRequest 时我最终选择了 CT 的方案。原因很简单,看看二者的代码行数吧: CTAPIBaseManager.h -> 49行 YTKBaseRequest.h -> 335行 说明一下,虽然命名不同,但二者在各自的框架中起到的作用是相似的。那么为什么有这么大的差距呢?因为 CT 使用了面向协议的编程思想,CTAPIBaseManager 的接口只提供最基本的请求操作方法,如- (NSInteger)loadData;- (void)cancelAllRequests;。而其他与请求弱相关或非必须实现的方法都交给了不同的代理对象,如paraHZYourcevalidator等。这样做的好处是极大地简化了接口,同时也不需要在基类的实现中写入一大堆空方法。YTK 使用了最传统的面向对象思想,作为基类的 YTKBaseRequest 必须实现所有原本是由子类覆写的方法。所以你会看到它的实现文件中有大量这样的方法,非常不优雅: 屏幕快照 2018-04-23 下午5.26.01

基于此,HZYNetwork 也选择了面向协议的思想,HZYRequest 基类只提供最基本与请求强相关的属性和方法。如多重请求策略strategy、服务service、urlurl,以及startcancel系列方法。将如回调代理、参数数据源、参数验证、数据重组等方法归集到各自的代理协议中,以代理的形式暴露。这样既简化了 HZYRequest 基类的接口、避免了大量无意义的空方法。同时这种设计思路也为今后的扩展提供了便利。

多个相同请求的处理

在业务中常见一种情况——列表的条件筛选。每一次用户改变筛选条件,都要通过一个稍微改变参数的请求来获取数据。当用户频繁改变条件的时候,可能会出现以下情况:请求已经发出,但尚未返回,此时用户修改了条件。另一种常见的情况是列表的下拉刷新:请求已经发出,但尚未返回,此时用户又进行了下拉刷新操作。上述两种类似的情况,对请求的处理却不应该相同。针对前者,实际上我们应该丢弃旧的请求,只等待最新发出的请求。而对于后者,则应该是放弃发出新的请求,等待第一个请求的响应。HZYNetworkReqeust有一个属性@property (nonatomic, assign) HZYRequestStrategy strategy;就是用来处理这种情况的,它提供了三个枚举:

typedef NS_ENUM(NSUInteger, HZYRequestStrategy) {
    /// 允许相同的请求多次发起
    HZYRequestStrategyAllowMultiple = 0,
    /// 在发起请求前检查如果有相同的请求尚未完成,则取消之前的请求
    HZYRequestStrategyCancelPreviousWhenStart,
    /// 在发起请求前检查如果有相同的请求尚未完成,则取消当前的请求
    HZYRequestStrategyDiscardIfPreviousOnLoading,
};

这使得重复请求的处理变得非常简单。而且实现也非常简单,只要在请求真正发出前执行如下方法即可

/**
 执行请求策略
 
 @return 是否终止当前请求,YES终止,NO执行
 */
- (BOOL)perforHZYtrategy {
    switch (self.strategy) {
        case HZYRequestStrategyDiscardIfPreviousOnLoading:
            if (self.isLoading) {return YES;}
            break;
            
        case HZYRequestStrategyCancelPreviousWhenStart:
            [self cancelAll];
            break;
            
        case HZYRequestStrategyAllowMultiple:
            break;
    }
    return NO;
}

限制请求的最大并发数

作为网络优化的一部分,我们希望控制请求的并发量。比如,当并发量限制为5时,同时发起了5个请求,如果这五个请求没有任何一个完成响应,那么再发起的请求应该处于等待状态,直到前五个请求中至少有1个响应完成。HZYNetwork维护了一个OperationQueue来达到这个目的。继承NSOperation实现了HZYNetworkTaskOperation来将请求任务和operation一一对应。在-main方法中执行task的-resume方法来发起请求。但这里遇到一个问题,NSOperationQueue中的operation在main方法执行完成后就从队列中退出了,而不是等到请求响应之后,因此请求发起后队列的位置就空出来了,这样就无法达到我们限制请求并发数的目的。这里的处理方式是用信号量。在初始化HZYNetworkTaskOperation时创建一个信号量为0的信号量,在执行resume后再执行dispatch_semaphore_wait方法等待,在请求响应后执行dispatch_semaphore_signal增加信号量,使-main方法不再被阻塞,operation执行完毕。这样就达到了限制最大并发的目的。