V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
xiaolingling123
V2EX  ›  iOS

基于 AFNetworking 的多范式网络请求管理器

  •  
  •   xiaolingling123 · 2017-01-04 17:21:33 +08:00 · 2312 次点击
    这是一个创建于 2661 天前的主题,其中的信息可能已经有所发展或是发生改变。

    HLNetworking: Multi paradigm network request manager based on AFNetworking

    简介

    基于 AFNetworking 的多范式网络请求管理器

    详细介绍地址: http://www.code4app.com/thread-12249-1-1.html

    HLNetworking 整体结构如图所示,是一套基于AFNetworking 3.1.0封装的网络库,提供了更高层次的抽象和更方便的调用方式。

    特性

    • 离散式的请求设计,方便进行组件化
    • 支持全局配置请求的公共信息
    • 提供大多数网络访问方式和相应的序列化类型
    • 提供 api 请求结果映射接口,可以自行转换为相应的数据格式
    • api 请求支持多种回调方式( block , delegate )
    • api 配置简单,通过 block 链式调用组装 api ,配合 APICenter 中的宏可以极大减少离散式 api 的设置代码
    • 支持批量请求、链式请求等特殊需求
    • 可随时取消未完成的网络请求,支持断点续传
    • 提供常用的 formData 拼接方式,可自行扩展
    • 提供请求前网络状态检测,重复请求检测,避免不必要的请求
    • 提供 debug 回调用于调试

    ##使用方法

    头文件的导入

    • 如果是通过 CocoaPods 安装,则:
    #import <HLNetworking/HLNetworking.h>
    
    • 如果是手动下载源码安装,则:
    #import "HLNetworking.h"
    

    全局网络配置

    [HLAPIManager setupConfig:^(HLNetworkConfig * _Nonnull config) {
    	config.request.baseURL = @"https://httpbin.org/";
    	config.request.apiVersion = nil;
    }];
    

    通过调用HLAPIManager+setupConfig:方法,修改 block 中传入的HLNetworkConfig对象来配置全局网络请求信息,其中可修改的参数如下:

    • tips:提示相关参数
      • generalErrorTypeStr:出现网络请求时使用的错误提示文字,该文字在 failure block 中的 NSError 对象返回;默认为:服务器连接错误,请稍候重试
      • frequentRequestErrorStr:用户频繁发送同一个请求,使用的错误提示文字;默认为:请求发送速度太快, 请稍候重试
      • networkNotReachableErrorStr:网络请求开始时,会先检测相应网络域名的 Reachability ,如果不可达,则直接返回该错误提示;默认为:网络不可用,请稍后重试
      • isNetworkingActivityIndicatorEnabled:请求时是否显示网络指示器(状态栏),默认为 YES
    • request:请求相关参数
      • apiCallbackQueue:自定义的请求队列,如果不设置则自动使用 HLAPIManager 默认的队列,该参数默认为 nil
      • defaultParams:默认的 parameters ,可以在 HLAPI 中选择是否使用,默认开启,该参数不会被覆盖, HLAPI 中使用setParams()后,请求的 params 中依然会有该参数,默认为 nil
      • defaultHeaders:默认的 header ,可以在 HLAPI 中覆盖,默认为 nil
      • baseURL:全局的 baseURL , HLAPI 的 baseURL 会覆盖该参数,默认为 nil
      • apiVersion: api 版本,用于拼接在请求的 Path 上,默认为 infoPlist 中的CFBundleShortVersionString,格式为v{version}{r},审核版本为 r ,例: http://www.baidu.com/v5/s?ie=UTF-8&wd=abc ,默认为 nil
      • isJudgeVersion:是否为审核版本,作用于 apiVersion ,存储在 NSUserDefaults 中, key 为 isR ,默认为 NO
      • userAgent: UserAgent , request header 中的 UA ,默认为 nil
      • maxHttpConnectionPerHost:每个 Host 的最大连接数,默认为 5
      • requestTimeoutInterval:请求超时时间,默认为 15
    • policy:网络策略相关参数
      • AppGroup:后台模式所用的 GroupID ,该选项只对 Task 有影响,默认为 nil
      • isBackgroundSession:是否为后台模式,该选项只对 Task 有影响,默认为 NO
      • isErrorCodeDisplayEnabled:出现网络请求错误时,是否在请求错误的文字后加上{code},默认为 YES
      • cachePolicy:请求缓存策略,默认为 NSURLRequestUseProtocolCachePolicy
      • URLCache: URLCache 设置,默认为 [NSURLCache sharedURLCache]
    • defaultSecurityPolicy:默认的安全策略配置,该配置在 debug 模式下默认为HLSSLPinningModeNone, release 模式下默认为HLSSLPinningModePublicKey,其中详细参数如下:
      • SSLPinningMode: SSL Pinning 证书的校验模式,默认为 HLSSLPinningModeNone
      • allowInvalidCertificates:是否允许使用 Invalid 证书,默认为 NO
      • validatesDomainName:是否校验在证书 CN 字段中的 domain name ,默认为 YES
      • cerFilePath: cer 证书文件路径,默认为 nil
    • enableReachability:是否启用 reachability , baseURL 为 domain ,默认为 NO

    API 相关

    组装 api

    // 组装请求
    HLAPI *get = [HLAPI API].setMethod(GET)
        							.setPath(@"get")
        							.setParams(@{@"user_id": @1})
        							.setDelegate(self);
    
    // 手动拼接 formData 上传
    HLAPI *formData = [HLAPI API].formData(^(id<HLMultipartFormDataProtocol> formData) {
        [formData appendPartWithHeaders:@{@"contentType": @"html/text"} body:[NSData data]];
    });
    
    // 使用 HLFormDataConfig 对象拼接上传
    [HLAPI API].formData([HLFormDataConfig configWithData:imageData
                                                     name:@"avatar"
                                                 fileName:@"fileName"
                                                 mimeType:@"type"]);
    

    block 方式接收请求

    // block 接收请求
    [get.success(^(id result) {
        NSLog(@"\napi 1 --- 已回调 \n----");
    })
     .progress(^(NSProgress *proc){
        NSLog(@"当前进度:%@", proc);
    })
     .failure(^(NSError *error){
        NSLog(@"\napi1 --- 错误:%@", error);
    })
     .debug(^(HLDebugMessage *message){
        NSLog(@"\n debug 参数:\n \
              sessionTask = %@\n \
              api = %@\n \
              error = %@\n \
              originRequest = %@\n \
              currentRequest = %@\n \
              response = %@\n",
              message.sessionTask,
              message.api,
              message.error,
              message.originRequest,
              message.currentRequest,
              message.response);
    }) start];
    

    delegate 方式接收请求

    // 当前类遵守 HLAPIResponseDelegate 协议
    // 在初始化方法中设置当前类为回调监听
    [HLAPIManager registerResponseObserver:self];
    
    // 在这个宏中写入需要监听的 api
    HLObserverAPIs(self.api1, self.api2)
    // 或者用-requestAPIs 这个代理方法,这两个完全等效
    - (NSArray<HLAPI *> *)requestAPIs {
        return [NSArray arrayWithObjects:self.api1, self.api2, self.api3, self.api4, nil];
    }
    
    // 在下面三个代理方法中获取回调结果
    // 这是成功的回调
    - (void)requestSucessWithResponseObject:(id)responseObject atAPI:(HLAPI *)api {
        NSLog(@"\n%@------RequestSuccessDelegate\n", api);
        NSLog(@"%@", [NSThread currentThread]);
    }
    // 这是失败的回调
    - (void)requestFailureWithResponseError:(NSError *)error atAPI:(HLAPI *)api {
        NSLog(@"\n%@------RequestFailureDelegate\n", api);
        NSLog(@"%@", [NSThread currentThread]);
    }
    // 这是进度的回调
    - (void)requestProgress:(NSProgress *)progress atAPI:(HLAPI *)api {
        NSLog(@"\n%@------RequestProgress\n", api);
        NSLog(@"%@", [NSThread currentThread]);
    }
    
    // 切记在 dealloc 中释放当前控制器
    - (void)dealloc {
        [HLAPIManager removeResponseObserver:self];
    }
    

    注意 1 :设置请求 URL 时,setCustomURL的优先级最高,其次是 API 中的setBaseURL,最后才是全局 config 中的baseURL,另无论是哪种baseURL都需要配合setPath使用。

    注意 2 :一次请求必须有{customURL}或者{config.baseURL | api.baseURL}``{api.path},如果{customURL}的参数错写成{api.path}中的无 host urlString ,也会被自动识别成{api.path}

    注意 3 :一个请求对象的回调 block (success/failure/progress/debug) 是非必需的(默认为 nil)。另外,需要注意的是, success/failure/debug 等回调 Block 会在 config 设置的 apiCallbackQueue 队列中被执行,但 progress 回调 Block 将在 NSURLSession 自己的队列中执行,而不是 apiCallbackQueue,但是所有的回调结果都会回落到主线程。

    注意 4 :请求的 delegate 回调之所以这样设置,是为了可以跨类获取请求回调,因此使用起来稍微麻烦一些,如果只需要在当前类拿到回调,使用 block 方式即可。

    注意 5 :HLAPI 同样支持其他 HTTP 方法,比如:HEAD, DELETE, PUT, PATCH 等,使用方式与上述类似,不再赘述。

    详见 HLNetworkConfigHLSecurityPolicyConfigHLAPIHLAPITypeHLAPIManagerHLFormDataConfigHLDebugMessage 等几个文件中的代码和注释,可选参数基本可以覆盖大多数需求。

    请求的生命周期方法

    // 在 api 组装时设置当前类为代理
    [HLAPI API].setDelegate(self)
    
    // 请求即将发出的代理方法
    - (void)requestWillBeSent {
        NSLog(@"willBeSent");
    }
    
    // 请求已经发出的代理方法
    - (void)requestDidSent {
        NSLog(@"didSent");
    }
    

    自定义请求结果处理逻辑

    // 指定的类需要遵守 HLObjReformerProtocol 协议
    [HLAPI API].setObjReformerDelegate(self);
    
    /**
     一般用来进行 JSON -> Model 数据的转换工作。返回的 id ,如果没有 error ,则为转换成功后的 Model 数据。如果有 error , 则直接返回传参中的 responseObject
    
     @param api 调用的 api
     @param responseObject 请求的返回
     @param error 请求的错误
     @return 整理过后的请求数据
     */
    - (nullable id)objReformerWithAPI:(HLAPI *)api 
                    andResponseObject:(id)responseObject
                             andError:(NSError * _Nullable)error
    {
    	if (responseObject) {
    		// 在这里处理获得的数据
    		// 自定义 reformer 方法
        	MyModel *model = [MyReformer reformerWithResponse:responseObject];
        	return model;
    	} else {
    		// 在这里处理异常
    		return nil;
    	}
    }
    
    

    取消一个网络请求

    // 通过 api 取消网络请求
    [self.api1 cancel];
    
    // 通过 HLAPIManager 取消网络请求
    [HLAPIManager cancel: self.api1];
    
    

    注意:如果请求已经发出,将无法取消,取消可以注销对应的回调 block ,但是 delegate 不会被注销。

    批量请求

    无序请求

    HLNetworking 支持同时发一组批量请求,这组请求在业务逻辑上相关,但请求本身是互相独立的,请求时并行执行,- batchAPIRequestsDidFinished 会在所有请求都结束时才执行,每个请求的结果由 API 自身管理。注:回调中的 HLAPIBatchRequests里的apiSet是无序的。

    HLAPIBatchRequests *batch = [[HLAPIBatchRequests alloc] init];
    // 添加单个 api
    [batch add:[HLAPI API]];
    // 添加 apis 集合
    [batch addAPIs:[NSSet setWithObjects:api1, api2, api3, nil]];
    
    [batch start];
    
    batch.delegate = self;
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5), dispatch_get_main_queue(), ^{
    	// 使用 cancel 取消
    	[batch cancel];
    });
    
    // batch 全部完成之后调用 
    - (void)batchAPIRequestsDidFinished:(HLAPIBatchRequests * _Nonnull)batchApis {
        NSLog(@"%@", batchApis);
    }
    

    链式请求

    HLNetworking 同样支持发一组链式请求,这组请求之间互相依赖,下一请求是否发送以及请求的参数可以取决于上一个请求的结果,请求时串行执行,- chainRequestsAllDidFinished 会在所有请求都结束时才执行,每个请求的结果由 API 自身管理。注:HLAPIChainRequests类做了特殊处理,自身即为HLAPI的容器,因此直接chain[index]即可获取相应的HLAPI对象,也可以直接遍历;回调中的 chainApis中元素的顺序与每个链式请求 HLAPI 对象的先后顺序一致。

    HLAPIChainRequests *chain = [[HLAPIChainRequests alloc] init];
    
    chain.delegate = self;
    
    [chain addAPIs:@[self.api1, self.api2, self.api3, self.api4, self.api5]];
    
    [chain start];
    
    for (id obj in chain) {
    	NSLog(@"%@", obj);
    }
    
    HLAPI *api = chain[0];
    
    // chain[0] == self.api1
    NSLog(@"%@", api);
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5), dispatch_get_main_queue(), ^{
    	// 使用 cancel 取消
    	[chain cancel];
    });
    
    // batch 全部完成之后调用 
    - (void)chainRequestsAllDidFinished:(HLAPIChainRequests *)chainApis {
        NSLog(@"chainRequestsAllDidFinished");
    }
    

    网络可连接性检查

    HLAPIManager 提供了八个方法和四个属性用于获取网络的状态,分别如下:
    
    // reachability 的状态
    typedef NS_ENUM(NSUInteger, HLReachabilityStatus) {
        HLReachabilityStatusUnknown,
        HLReachabilityStatusNotReachable,
        HLReachabilityStatusReachableViaWWAN,
        HLReachabilityStatusReachableViaWiFi
    };
    
    // 通过 sharedMager 单例,获取当前 reachability 状态
    + (HLReachabilityStatus)reachabilityStatus;
    // 通过 sharedMager 单例,获取当前是否可访问网络
    + (BOOL)isReachable;
    // 通过 sharedMager 单例,获取当前是否使用数据流量访问网络
    + (BOOL)isReachableViaWWAN;
    // 通过 sharedMager 单例,获取当前是否使用 WiFi 访问网络
    + (BOOL)isReachableViaWiFi;
    
    // 通过 sharedMager 单例,开启默认 reachability 监视器, block 返回状态
    + (void)listening:(void(^)(HLReachabilityStatus status))listener;
    
    // 通过 sharedMager 单例,停止 reachability 监视器监听 domain
    + (void)stopListening;
    
    // 监听给定的域名是否可以访问, block 内返回状态
    - (void)listeningWithDomain:(NSString *)domain listeningBlock:(void (^)(HLReachabilityStatus))listener;
    
    // 停止给定域名的网络状态监听
    - (void)stopListeningWithDomain:(NSString *)domain;	
    

    注意:reachability 的监听 domain 默认为[HLNetworking sharedManager].config.baseURL ,当然你也可以通过对象方法自定义 domain 。

    HTTPS 请求的本地证书校验( SSL Pinning )

    在你的应用程序包里添加 (pinned) 相应的 SSL 证书做校验有助于防止中间人攻击和其他安全漏洞。HLNetworkingconfig属性和HLAPI里有对 AFNetworking 的 AFSecurityPolicy 安全模块的封装,你可以通过配置configdefaultSecurityPolicy属性,用于校验本地保存的证书或公钥可信任。

    // SSL Pinning
    typedef NS_ENUM(NSUInteger, HLSSLPinningMode) {
        // 不校验 Pinning 证书
        HLSSLPinningModeNone,
        // 校验 Pinning 证书中的 PublicKey
        HLSSLPinningModePublicKey,
        // 校验整个 Pinning 证书
        HLSSLPinningModeCertificate
    };
    
    // 生成策略
    HLSecurityPolicyConfig *securityPolicy = [HLSecurityPolicyConfig policyWithPinningMode:HLSSLPinningModePublicKey];
        // 是否允许使用 Invalid 证书,默认为 NO
        securityPolicy.allowInvalidCertificates = NO;
        // 是否校验在证书 CN 字段中的 domain name ,默认为 YES
        securityPolicy.validatesDomainName = YES;
        //cer 证书文件路径
        securityPolicy.cerFilePath = [[NSBundle mainBundle] pathForResource:@"myCer" ofType:@"cer"];
    
    // 设置默认的安全策略
    [HLAPIManager setupConfig:^(HLNetworkConfig * _Nonnull config) {
        config.defaultSecurityPolicy = securityPolicy;
    }];
    
    // 针对特定 API 的安全策略
    self.api1.setSecurityPolicy(securityPolicy);
    

    注意:API 中的安全策略会在此 api 请求时覆盖默认安全策略,并且与 api 相同 baseURL 的安全策略都会被覆盖。

    Task 相关

    HLTask 目前支持上传下载功能,已支持断点续传,其中上传是指流上传,即使用 UPLOAD 方法;如果需要使用 POST 中的 formData 拼接方式上传,请参考 API 相关的 formData 设置

    config 设置

    [HLTaskManager setupConfig:^(HLNetworkConfig * _Nonnull config) {
    	config.baseURL = @"https://httpbin.org";
    	config.isBackgroundSession = NO;
    }];
    [HLTaskManager registerResponseObserver:self];
    

    链式调用组装 Task

    HLTask *task = [[HLTask task].setDelegate(self)
    	 // 设置 Task 类型, Upload/Download
    	 .setTaskType(Upload)
    	 // 设置下载或者上传的本地文件路径
        .setFilePath([[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"Boom2.dmg"])
        // 设置下载或者上传的地址
        .setTaskURL(@"https://dl.devmate.com/com.globaldelight.Boom2/Boom2.dmg") start];
    

    Task 的生命周期方法

    [HLTask task].setDelegate(self)
    
    #pragma mark - task request delegate
    // 请求即将发出
    - (void)requestWillBeSentWithTask:(HLTask *)task {
        
    }
    // 请求已经发出
    - (void)requestDidSentWithTask:(HLTask *)task {
        
    }
    

    请求回调代理

    [HLTaskManager shared].responseDelegate = self;
    
    #pragma mark - task reponse protocol
    // 设置监听的 task
    HLObserverTasks(self.task1)
    // 等同于 HLObserverTasks(...)
    - (NSArray <HLTask *>*)requestTasks {
        return [NSArray arrayWithObjects:self.task1, nil];
    }
    
    // 下载 /上传进度回调
    - (void)requestProgress:(nullable NSProgress *)progress atTask:(nullable HLTask *)task {
        NSLog(@"\n 进度=====\n 当前任务:%@\n 当前进度:%@", task.taskURL, progress);
    }
    
    // 任务完成回调
    - (void)requestSucessWithResponseObject:(nonnull id)responseObject atTask:(nullable HLTask *)task {
        NSLog(@"\n 完成=====\n 当前任务:%@\n 对象:%@", task, responseObject);
    }
    
    // 任务失败回调
    - (void)requestFailureWithResponseError:(nullable NSError *)error atTask:(nullable HLTask *)task {
        NSLog(@"\n 失败=====\n 当前任务:%@\n 错误:%@", task, error);
    }
    

    注意 1 :Task 暂时不支持批量上传 /下载。

    注意 2 :Task 的 resume 信息记录在沙盒中Cache/com.qkhl.HLNetworking/downloadDict 中

    Center 相关

    • HLAPICenter提供一种离散式 API 的组织模版,其核心理念是通过 category 分散 APICenter 内的 API 对象;
    • HLBaseObjReformer提供了基于 YYModel 的 JSON->Model 的模版;
    • 通过HLAPIMacro中定义的宏,可以快速设置模块所需的 API

    范例

    • 根据 API 相关中的设置,配置 HLAPIManager 的相关 Config
    • 根据模块创建HLAPICenter的 category ,例如HLAPICenter+home
    • 在 HLAPICenter+home.h 中使用 HLStrongProperty(name)宏, name 为方法名,形如:
    #import "HLAPICenter.h"
    
    @interface HLAPICenter (home)
    HLStrongProperty(home)
    @end
    
    • 在 HLAPICenter+home.m 中使用 HLStrongSynthesize(name, api)宏, name 为方法名, api 为 API 对象,形如:
    #import "HLAPICenter+home.h"
    
    @implementation HLAPICenter (home)
    HLStrongSynthesize(home, [HLAPI API]
                       .setMethod(GET)
                       // 根据需要设置 Path 、 BaseURL 、 CustomURL
                       .setPath(@"index.php?r=home")
                       // 如果该 api 对应的 model 可以直接通过 yymodel 转换的话,则指定需转换的模型类型名
                       .setResponseClass(@"HLHomeModel")
                       // 这里使用 self.defaultReformer 即通过 yymodel 转换
                       .setObjReformerDelegate(self.defaultReformer))
    @end
    
    • 然后就可以愉快的使用了,在控制器中#import "HLAPICenter+home.h",按如下方法使用即可:
    - (void)testHome {
        [HLAPICenter.home.setParams(@{@"user_id": @self.myUserID})
        .success(^(HLHomeModel *model) {
            self.model = model;
        }).failure(^(NSError *obj){
            NSLog(@"----%@", obj);
        }) start];
    }
    

    更新日志

    1.2.2

    新增:
    1. 拆分了 HLNetworkConfig 内的参数,现分为 tips 、 request 、 policy 、 defaultSecurityPolicy 、 enableReachability 这五个大选项
    修复:
    1. 修复了 HLObserverAPIs(...)和 HLObserverTasks(...)内传入 nil 引起的崩溃错误
    2. 修复了 HLAPI 中 setResponseClass 方法传入无效类名引起的崩溃错误,当该类名无效时, HLBaseObjReformer 将不会做任何操作,直接返回 nil
    3. 修复了 HLAPI 中 setCustomURL 方法传入无效 urlString 引起的崩溃错误
    

    环境要求

    该库需运行在 iOS 8.0 和 Xcode 7.0 以上环境.

    集成方法

    HLNetworking 可以在CocoaPods中获取,将以下内容添加进你的 Podfile 中后,运行pod install即可安装:

    pod "HLNetworking"
    

    如果你只需要用到 API 相关,可以这样:

    pod "HLNetworking/API"
    

    目前有四个模块可供选择:

     - HLNetworking/Core
     - HLNetworking/API
     - HLNetworking/Task
     - HLNetworking/Center
    

    其中Core包含APITask的所有代码,APITask相互独立,Center则依赖于API

    作者

    wangshiyu13, [email protected]

    freefcw
        1
    freefcw  
       2017-01-04 18:21:38 +08:00
    虽然不写 ios 了,但纯技术的,还是支持一下
    zhangchioulin
        2
    zhangchioulin  
       2017-01-05 09:30:00 +08:00 via Android
    支持下。什么叫做多范式网络请求?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   4983 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 09:42 · PVG 17:42 · LAX 02:42 · JFK 05:42
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.