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

2017-01-04 17:21:33 +08:00
 xiaolingling123

简介

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

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

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

特性

##使用方法

头文件的导入

#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对象来配置全局网络请求信息,其中可修改的参数如下:

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 相关

范例

#import "HLAPICenter.h"

@interface HLAPICenter (home)
HLStrongProperty(home)
@end
#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
- (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, wangshiyu13@163.com

2314 次点击
所在节点    iOS
2 条回复
freefcw
2017-01-04 18:21:38 +08:00
虽然不写 ios 了,但纯技术的,还是支持一下
zhangchioulin
2017-01-05 09:30:00 +08:00
支持下。什么叫做多范式网络请求?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/332213

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX