怎么用HTTP/2优化iOS APP网络层次架构?

原文链接:http://www.tbqw.com/art/175230.html

HTTP/2,是HTTP协定发布后的首个更新,于2015年2月17日被批准。它采取了一系列优化技术来总体晋升HTTP协定的传输机能,如异步连接复用、头紧缩等等,堪称是当前互联网利用开发中,网络层次架构优化的必选方案之一。


Apple至于HTTP/2的态度也特别踊跃,5月HTTP/2正式发表后不久,便在紧接着6月召开的WWDC 2015大会中,向全世界开发者宣告,iOS 9 开始支撑HTTP/2。虽然Apple早早地宣告支撑HTTP/2,然而现在全部技术圈内提及的iOS网络层架构设计还大多数停留在HTTP 1.1时期,并无一个与时俱进的、包括HTTP/2优化的网络层架构设计谋略。

至于架构设计,我曾经说过,脱离业务谈架构就是纯洁的耍流氓。因而,架构的设计必定要结合当前的业务需求来进行设计以及计划,并且做好必定的可扩大性,以应答未来的变化。

本文会结合当前饿了么的业务谈谈下列几个方面内容:

  1. 如何在iOS下使用HTTP/2?

  2. 如何设计一个iOS的网络层架构?

  3. 与时俱进下,咱们的解决方案?



与时俱进:HTTP/2下的iOS网络库




挪动真个APP,网络层是一个几近完整不可或者缺的角色。而也恰是在网络层,因为不同家的业务模型、业务结构不同样,使得网络层的架构显现一种百家争鸣的局面。另外一方面,Apple对网络层的API也是有对比好的封装;即便你不是太熟识Apple的网络API,使用业界时尚的AFNetworking或ASIHttpRequest也是可以简化很多的操作。无非后者的作者已多年不保护了,因而AFNetworking基本上已成了iOS APP的标配。 

Apple在CocoaTouch层基于CFNetworking库提供的网络API有两个大类:NSURLConnection以及NSURLSession。后者从iOS7开始呈现,并声称是NSURLConnection的替换者。跟着时间的推移,WWDC 2015的召开也正式宣告了iOS9中NSURLConnection的deprecated标注,完成为了其历史使命,也兑现了以前的许诺。

无非在实际的开发进程中,咱们可以发现,虽然标注了deprecated,其实不象征着NSURLConnection的库不可以继续使用;而且,因为习气性问题,仍是有许多的工程师仍旧执着于NSURLConnection所带来的熟识味道;尤其是使用AFNetworking库的工程师们,因为AFNetworking至于NSURLConnection的封装特别精美,并且可以依据业务需要自定义添加相应的依赖关联,使得其在实际利用中让人感到无比的“舒服”。

但是,渔与熊掌老是不可兼得。WWDC 2015 Session711 告知咱们,从 iOS 9 才开始支撑的 HTTP / 2 协定只能在 NSURLSession 中使用。这也就象征着,要想进化到 HTTP / 2 就不能不舍弃陪伴咱们多年的 NSURLConnection ,而且还要将设计网络层架构的思惟方式调剂到 NSURLSession 上来。

无非庆幸的是,AFNetworking从2.0开始也提供了NSURLSession的实现版本,并且相干的 API 并无太大的变动,至于一般性的迁移仍是能够轻松应答;并且,从AFNetworking 3.0开始,正式遗弃NSURLConnection,全面投入NSURLSession的怀抱。 

如果在您的网络层设计中,采取了AFNetworking来下降设计的繁杂性,那末正如前面提到的,因为二者在 API 方面并无太大的悬殊,因而在一般的网络层迁移进程中可以平滑地过渡。而如果你在网络层设计中直接采取了原生的 API,也不需要耽心,由于 NSURLSession 的 API 被设计的更为美好,也更为易用。无非,虽然NSURLSession有特别多值得称颂之处,然而它毕竞是一种新的设计思想以及理念。因而在实际的使用进程中咱们会发现许多与以前设计思惟相抵牾之处,特别是在网络依赖性处理上,就连AFNetworking也做得不是太好,这点咱们在后面的章节中还会再次提到。 

综合以上而言,技术的脚步永久向前,仅凭 HTTP / 2 这一点,咱们相信 Apple 也必定会把重心向 NSURLSession 偏移,继续优化她,而咱们也要跟上历史的车轮,是时候让迟暮的 NSURLConnection 休息了。

发展进击:iOS网络层的架构设计




架构的设计老是以及业务的发展相结合以及适应的。在饿了么挪动多款App的发展进程中,因为不同业务的悬殊性致使接口、协定等都有不同的需求,给多款App设计出一个具有干净API以及高度内耦合的网络层成了一项挑战,而这个设计也将直接影响咱们APP业务工程师们的开发效力。这节主要论述理论,抛出一些问题。在下一节会给出结合饿了么多款APP的业务下所设计的网络层的解决方案。 

本节咱们主要讨论两点:

  1. 与业务相结合的网络层设计

  2. 与安全相干的网络层设计 

与业务相结合的网络层设计


先来看与业务相结合的网络层设计。 

与业务相连接最紧密之处必定是输入与输出,而网络层的功能无疑是接受输入的数据,挑拣一个相应的通道组装数据发送给服务器,然后将服务器返回的数据返回给上一层,即输出数据。接下来咱们从数据的角度来看看在网络层设计中需要斟酌些啥样的问题。这里咱们会从下列三个方面来进行论述: 

  1. 数据输入

  2. 数据回调

  3. 数据转换

  • 数据输入

首先是输入进程。业务数据调用网络层接口时可以称之为输入,这里一般会有两种情势的设计。 

第一种对比常见,许多时候会被称为集中式的API处理,行将一些时常使用的网络层调用的代码封装成一到两个函数供上层调用。上层输入相干的参数便能获得相干的回调。如下列函数: 

+ (void)networkTransferWithURLString:(NSString *)urlString

                       andParameters:(NSDictionary *)parameters

                              isPOST:(BOOL)isPost

                        transferType:(NETWORK_TRANSFER_TYPE)transferType

                   andSuccessHandler:(void (^)(id responseObject))successHandler

                   andFailureHandler:(void (^)(NSError *error))failureHandler {

                   // 封装AFN

                   }



另外一种情势的设计,则采取一种继承情势设计每个API,而每个API都对应一个类,这个类中将该API的所有参数都设定好,并提供“开始”接口以及“返回”的Block,许多时候咱们称这类为散布式的API处理。一个对比通用的BaseAPI可以有以下可配置项:

typedef NS_ENUM(NSUInteger, DRDRequestMethodType) {

    DRDRequestMethodTypeGET     = 0,

    DRDRequestMethodTypePOST    = 1,

    DRDRequestMethodTypeHEAD    = 2,

    DRDRequestMethodTypePUT     = 3,

    DRDRequestMethodTypePATCH   = 4,

    DRDRequestMethodTypeDELETE  = 5

};

@interface DRDBaseAPI : NSObject

@property (nonatomic, copy, nullable) NSString *baseUrl;

@property (nonatomic, copy, nullable) void (^apiCompletionHandler)(_Nonnull id responseObject,  NSError * _Nullable error);

- (DRDRequestMethodType)apiRequestMethodType;

- (DRDRequestSerializerType)apiRequestSerializerType;

- (DRDResponseSerializerType)apiResponseSerializerType;

- (void)start;

- (void)cancel;

...

@end



每个具体的API均可以继承自这个BaseAPI,当上层业务需要进行网络调历时,实例化一个需要调用的API接口,对返回的Block进行编码,同时开启接口。如下列代码: 

DRDAPIPostCall *apiPost = [[DRDAPIPostCall alloc] init];

[apiPost setApiCompletionHandler:^(id responseObject, NSError * error) {

}];

[apiPost start];


这两种网络层接口的设计其实都是对应不同的业务所发生出来的思惟,因而必定都有其优缺陷。例如,第一种情势的接口其优点在于简单粗鲁,合用于业务逻辑相对于简单并且统一的RESTFUL API网络接口。然而缺陷也特别显明,一旦赶上略微繁杂一些的网络接口情况,便需要在ViewController里写入大量的逻辑来到达目的。这也同时会使得本来就臃肿不堪的ViewController变得更为的庞大。

第二种的设计则俊雅很多。将大量的配置逻辑都放在了另外一个类文件中进行设计,ViewController中的代码会变得更轻快一些。而将配置放在类中还有另外一个益处,那便是可以增添很多平时不使用的可配置项,来增添全部网络层的可扩大性;与此同时,每个API对应了一个不同的类的设计,又可让不同的API可以有不同的表象,如分别遵守不同的JSON-RPC版本。

无非这类情势的设计也存在惊心动魄的问题,那便是类爆炸。如果是小型的APP,则问题其实不是那末显明;而如果是中大型APP的话,动则上百个API,会使得后期的保护变得有些费劲。 

  • 数据回调

说完了输入问题,接下来输出的设计。在输出部份的设计中,可以说是八仙过海各显神通。 

网络层的传输大多以异步加载为主,即服务器响应后由网络层来负责将数据推给上层业务线程。在iOS的体系中,也提供了许多种方式用于这类场景的处理,例如直接播送的Notification、函数回调的delegate和最具特点的Block,都能够完成这类任务。那末采取哪类方式呢?在回答这个问题前咱们先来看看这几种方式其各自的优缺陷。 

  • Notification

Notification,顾名思义的播送,其特色在于一对多地发送相干数据的通知。优点特别显明,易于实现;但缺陷也很显明,会损坏全部APP架构设计中的层次结构,造成跨层的调用以及处理。 

  • Delegate

Delegate,最经常使用的的回调方式。优点是后期易于保护且不会造成跨层的调用;缺陷则是回调代码与输入的逻辑代码大部份时候不会放在一块儿,增添了一些后期浏览上的本钱。 

  • Block

Block是OC语言中的特性,其优点刚好是Delegate的缺陷,即它让回调的代码能够以及调用的代码维持在相同位置,利于静态代码追踪以及逻辑思惟的持续。缺陷则在于容易造成循环援用(Retain Cycle);并且至于大型APP来讲,埋点这类AOP行动通常在Block中难以为继,且会造成Debug上的一些难题。 

在Block的使用进程中,必定要注意使用weakSelf以及strongSelf来打破循环援用。否则酿成的内存泄露会造成后期排查的难题。

  • 小结

或许读者看到这会更困惑了,究竞啥样的方案更佳?个人认为仍是要从业务需求动身来进行设计,从我本身而言我更喜爱Block+Notification的情势,然后在适量的时候辅以Delegate完成。 

  • 数据转换

数据回调的问题已基本解决,然而新的问题也摆在了咱们的眼前:上层该看到怎么的数据? 

在这里咱们会发现特别多的利用场景,譬如大多数情况下,业务层都但愿返回的是与其本身相干的数据结构(Model实例),在这样的条件下能够特别处所便地对本地的数据进行相干的操作;而又譬如说查询一个操作结果的是与非,那末自身数据就只有一个yes或no,这时候候采取一个数据结构来包括便会显得繁杂以及臃肿;又或者是网络层采用了JSON-RPC这样的协定,返回回来的信息存在大量的冗余数据,但上层业务却是若水万千只取一瓢饮。 

从以上各种场景中咱们可以看到,业务所需的数据情势特别多变,因而最佳的方式仍是交给上层自我去处理。一种常见的法子就是设定一个Delegate或Block进行返回数据的转换,将JSON或XML等格式转成所需要的数据格式以利便上层业务继续处理。无非我个人更偏向于在API自身就实现好这个Delegate或Block所描写的转换函数,这样会让API的层次更为清晰。下一节我会谈到咱们的处理方式。

与安全相干的网络层设计


接下来咱们来看看与安全性相干的设计。其实整体来讲,使用了HTTPS基本上就已足够保证你的网络安全性了,这里我也就不逐一罗列其益处。事实上国外多数的大公司和国内的BAT几近都已是全站HTTPS了,免费的SSL证书的申请难度也在不断下降,门坎上已可以说是没有门坎。因而为了站点的安全性,上HTTPS吧。 

无非,HTTPS如果使用不当依然会存在一些的小缺点,MITMA(Man-in-the-middle attack)袭击便是其中的一种。尝试这样一种情况,使用Charles这样的抓包工具来抓取HTTPS的包,Charles会让咱们去安装它自我颁发的根证书。一旦咱们选择以及信任了这个根证书,咱们会发现Charles能够顺利地显示全部HTTPS通讯的情况了。

至于这类中间人袭击,目前一般的解决方案即采用SSL Pinning,行将服务器的公钥证书与全部APP打包在一块儿发出,然后在网络要求时候将服务器发送过来的证书与本地证书进行对比,从而防止中间人袭击的可能性。关于这部份的设计,AFNetworking已有相干的实现了,我在《正确使用AFNetworking的SSL保证网络安全》有过详细论述,这里就再也不赘述了。 

3  来点实际的:解决方案




说完了理论,现在结合实际来谈谈咱们的解决方案。 

咱们要使用HTTP/2,那末在网络库的选择上必定需要使用NSURLSession来到达目的,并且咱们也不但愿自我去实现序列化和RESTFUL的繁杂性,因而AFNetworking3.0成为了一个对比不错的选择。然而仿佛仅唯一这些还不够。接下来会分为下列几个部份来谈谈咱们的解决方案: 

  • 业务协定

  • 输入与配置

  • 数据转换与输出

  • 安全

业务协定


从业务协定上来讲,饿了么众多APP中,每一款APP都有其本身的特色,例如有些采用RESTFUL的设计,也有采取JSON-RPC的设计来到达业务目的。这时候候如果采用集中式的API设计,相对于应JSON-RPC会发生大量的RPC协定封装代码。并且至于不同版本、类型的RPC协定,需要有不同的集中函数或增添大量的参数来处理其中的悬殊性。

如果采用散布式的API设计,则可以将这部份协定代码放进API本身类中来进行处理。在这里,我设计了一个RPCProtocol,由业务方自我来定义所需要遵守的业务RPC标准。而每一个API都储存一个rpcDelegate字段来自定义自我的上层协定,而如果为空时,即代表着不进行RPC封装而是直接发送,从而到达JSON-RPC以及RESTFUL在一个APP共存的目的;并且因为每一个API均可以指定不同的rpcDelegate,因而可以合用于服务器端不同的RPC版本兼容性。这里,RPCProtocol会有一些这样的论述: 

NS_ASSUME_NONNULL_BEGIN

@protocol DRDRPCProtocol <NSObject>

- (nullable NSString *)rpcRequestUrlWithAPI:(DRDBaseAPI *)api;

- (nullable id)rpcRequestParamsWithAPI:(DRDBaseAPI *)api;

- (nullable id)rpcResponseObjReformer:(id)responseObject withAPI:(DRDBaseAPI *)api;

- (nullable id)rpcResultWithFormattedResponse:(id)formattedResponseObj withAPI:(DRDBaseAPI *)api;

- (NSError *)rpcErrorWithFormattedResponse:(id)formattedResponseObj withAPI:(DRDBaseAPI *)api;

@end


NS_ASSUME_NONNULL_END

Protocol中会对RequestURL,RequestParams进行RPC装箱设计,并且至于回包,也有rpcResponseObjReformer进行拆箱,将可用值以及过错值交给rpcResultWithFormattedResponse和rpcErrorWithFormattedResponse处理后,再返回给业务上层。 

通过使用RPCProtocol,咱们维持了全部网络层上层协定的一种可扩大性。

输入与配置


解决完协定的问题,咱们再来看看输入以及配置的问题。 

简单一看,仿佛输入以及配置的关联其实不大;的确如斯,在集中式的API设计时,更多的时候是传参,那末现在采用扩散式API设计时,因为每一个API先继承BaseAPI,然后再在子类中去笼盖每一个需要配置的函数,因而看起来每一个API都更像是一个配置的进程。 

配置完成后的每一个API,过去的方式多是每一个API都对应一个APIManager,反馈到AFNetworking上呢,可能就是每一个API都使用一个AFHTTPRequestOperationManager,然后在这个Manager去发起要求。 

无非,这类情势在HTTP/2上会显得愚笨。咱们都了解HTTP/2是复用TCP管道连接的,这点体现在NSURLSession底层至于每一个session是对多个task进行连接的复用。

如果继续采用过去的方式多个AFHTTPSession来要求,会致使多个TCP连接,并且连接数不可控。而复用Session的话,可以充沛应用NSURLSession的并发节制和HTTP/2的高复用来提高机能。这点我在我的另外一片文章《别说你会AFNetworking3.0/NSURLSession》有过详细论述,这里也再也不赘述了。 

因而,我这里将每一个配置好的API都扔到一个同享的APIManager中,即扩散式API回归到集中式调用的怀抱。由APIManager来负责提供SessionManager的策略。并且通过Global的配置,来抉择每一个Session的最大并发数。同时将AFNetworking封装进全部APIManager,维持对外透明。

这样未来如果进级AFNetworking版本,或打算切换到直接使用NSURLSession来处理网络连接,上层业务API也不需要有任何的改动,进一步增强了未来的可配置性。 

上节咱们提到了扩散式API,它拥有一个最大的缺陷,那便是致使类的爆炸,发生出成白上千的API的文件。在这一点上,我设计了另外一个BaseAPI,我称之为GeneralAPI。这个API与以前的有啥不同呢?先来看一个典型的GET要求API的类文件内容: 

- (NSString *)requestMethod {

    return @"get";

}

- (id)requestParameters {

    return nil;

}

- (DRDRequestMethodType)apiRequestMethodType {

    return DRDRequestMethodTypeGET;

}

- (DRDRequestSerializerType)apiRequestSerializerType {

    return DRDRequestSerializerTypeHTTP;

}

- (DRDResponseSerializerType)apiResponseSerializerType {

    return DRDResponseSerializerTypeHTTP;

}



在这个API里,笼盖好几个函数便可以完成相应的内容。而在ViewController中进行调用会有这样的代码。 

   
 DRDAPIGetCall *apiGet = [[DRDAPIGetCall alloc] init];

    [apiGet setApiCompletionHandler:^(id responseObject, NSError * error) {

        NSLog(@"responseObject is %@", responseObject);

        if (error) {

            NSLog(@"Error is %@", error.localizedDescription);

        }

    }];

    [apiGet start];



而如果使用GeneralAPI,则在ViewController中,会是这样的代码。 

DRDGeneralAPI *apiGeGet            = [[DRDGeneralAPI alloc] initWithRequestMethod:@"get"];

apiGeGet.apiRequestMethodType      = DRDRequestMethodTypeGET;

apiGeGet.apiRequestSerializerType  = DRDRequestSerializerTypeHTTP;

apiGeGet.apiResponseSerializerType = DRDResponseSerializerTypeHTTP;

[apiGeGet setApiCompletionHandler:^(id responseObject, NSError * error) {

    NSLog(@"responseObject is %@", responseObject);

    if (error) {

        NSLog(@"Error is %@", error.localizedDescription);

    }

}];

[apiGeGet start];



没错,使用GeneralAPI,将一些简单的,不繁杂的API的配置,直接使用property来直接在ViewController中赋值,这样就下降了一些简单的API生成类文件致使的爆炸。增强了全部网络层的易用性以及简便性。

数据转换与输出


再来看看数据转换与输出。 

这里咱们将数据转换以及输出放在一块儿来讨论。上节提到的交付啥样的数据给业务层、数据转换的操作,但并无给出谜底。现在咱们来结合业务来看看怎么来操作数据的转换。 

前面提到过,饿了么各个APP产品线都会有自我的性情以及特色,数据转换上也会显现各自喜好的局面。有人喜爱用Mantle,有人用过MJExtension,也有采用YYModel,也有大牛自我实现JSON<-->Model的转换。因而,网络层最佳其实不过问数据的转换方式和进程,而是提供一个机会给上层业务来让其自我采取自我的方式进行转换。因而,我在API的设计中,提供了这样一个函数: 

- (nullable id)apiResponseObjReformer:(id)responseObject andError:(NSError * _Nullable)error;



GeneralAPI中对应为: 

@property (nonatomic, copy, nullable) id _Nullable (^apiResponseObjReformerBlock)(id responseObject, NSError * _Nullable error);



这个函数默许为空,参数中的responseObject为rpcDelegate拆包后发生的resposneObject。在这个函数中,上层业务可以将responseObject进行Model的转换工作,将Model作为返回值交给apiCompletionHandler函数进行操作。这样既维持了ViewController中的简洁性,也在保证了各个上层业务至于JSON<-->Model转换的多样性的同时,保证了未来转换方式的可扩大性。

安全


最后来谈谈安全。 

其实安全到这一块可谈的已不多了,该谈的都在上节中谈完了。因为APIManager中采取了AFNetworking简化SSL Pinning的繁杂度,因而在网络层中只需要三步即可以完成SSL Pinning。 

  1. 实例化一个DRDSecurityPolicy, 将SecurityPolicy中的DRDSSLPinningMode设置成为DRDSSLPinningModePublicKey或DRDSSLPinningModeCertificate。

  2. 将API的apiSecurityPolicy设定为以上实例。

  3. 将服务器的公钥证书放到APP Bundle中。

收场!

  • 彩蛋:One More Thing!

**多网络要求的并发履行。 **

假想这样一个场景,咱们但愿有若干个网络要求,当这些要求都收场后才通知上层利用工作的完成。 

初期采用AFNetworking的AFHTTPRequestOperationManager方案时,AFN对全部系统都采取了NSOperation和NSOperationQueue来节制网络要求的依赖性。然而HTTP/2后的NSURLSession因为没法使用这类设计,因而造成这类并发依赖难以为继。 

值得庆幸的是,AFNetworking在SessionManager的实现中照旧保存有dispatch_group_t的接口。因而咱们使用dispatch_group创立了DRDAPIBatchAPIRequests类,来到达并发网络要求的目的。 

@interface DRDAPIBatchAPIRequests : NSObject

@property (nonatomic, strong, readonly, nullable) NSMutableSet *apiRequestsSet;

@property (nonatomic, weak, nullable) id<DRDAPIBatchAPIRequestsProtocol> delegate;

- (void)addAPIRequest:(nonnull DRDBaseAPI *)api;

- (void)addBatchAPIRequests:(nonnull NSSet *)apis;

- (void)start;

@end



一如既往,维持API层面简洁。这里咱们使用了Delegate来处理多个网络要求完成后的回调操作: 

@protocol DRDAPIBatchAPIRequestsProtocol <NSObject>

- (void)batchAPIRequestsDidFinished:(nonnull DRDAPIBatchAPIRequests *)batchApis;

@end



在这个设计中,每一个API完成后,均可以有自我的回调。所有的并发网络要求完成后仍旧可以有一个公用的回调,让总体设计维持一个离散+集中都能得到很益处理的情景,保证上层的业务可扩大性。

总之,

结合业务,才能谈及架构;延续调优,才能不断地与时俱进。 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值