http://geek.csdn.net/news/detail/135822
【导语】伴随着业务场景需求的变化,58 同城 App 在网络架构层面经历了从使用第三方开源网络框架到自主研发框架的不同阶段的不断改进。本文作者即从 iOS 开发角度具体分享了 58 同城移动客户端在网络框架层面的几次演变改进实践与经验总结。
成熟的互联网企业 App 往往离不开对网络数据交互的依赖,因此一个封装良好且健壮易用的网络框架不可或缺。本文从 iOS 开发角度来概说 58 同城移动客户端在网络框架层面的几次演变改进实践,希望能给大家带来些许具有参考价值的分享经验。
大部分 iOS 开发者知道的两个第三方开源网络框架 AFNetworking(后文简称 AFN)和 ASIHTTPRequest,都是基于 Apple iOS/OS X 底层网络接口的封装,友好的接口设计又不乏健壮实用,因此备受 iOS 开发者喜爱,同时被诸多大型 App 直接集成使用。可惜后者在 2012 年便停止更新了,而 AFN 多年来却一直保持不断地改进更新,以致一些近两年初入 iOS 行当的开发者就只知 AFN 了。AFN 确实是一个值得学习的成熟网络框架,这里不做过多探讨,大致介绍其工作在系统框架的所属层次,以便于下文讲解我们的网络框架原理,如图 1 所示。
AFN 在 3.0 版本之前都是基于 NSURLConnection 封装,3.0 之后大刀阔斧般地修改,加入了对 NSURLSession 的支持,后文再来详说这个 Apple 在 iOS 7 以后开放的新类。
曾经有人质疑道,真正稳定健壮的框架是无需经常变更的,为什么你们的网络框架会经历几次大改版?到底又是什么在驱动框架不断改进呢?依照我们的经历来看,主要总结为两个原因:
本文就以时间为坐标来依次解析我们在每次改版中遇到了什么样的问题,又是出于怎样的考虑并着手解决的。
我们曾经和绝大多数 App 一样,直接使用现成的开源框架 AFN。但随着业务不断扩展,单靠直接使用 AFN 的接口逻辑已明显不能满足我们的需求,同时对于后期的维护也会带来诸多不便。主要表现为:
针对以上几点,下文具体阐述其中的缘由。我们的工程中类似这样的网络请求代码曾经随处可见(如图 2 所示)。
业务模块对于 AFHTTPRequestOperation 的依赖过于严重,在整个工程内全局检索一遍大致存在几百处。然而这种直接引用第三方库的方式在遇到其重大更新时便会带来灾难性的修改,显然 AFN 3.0 在去掉 AFHTTPRequestOperation 这个类以及旧接口时,相信许多 App 都深受其苦。我们在 AFN 3.0 版本更新之前就考虑到了此类问题并预先进行了调整,解决方案如图 3 所示。
正如图 3 所示,二次封装 AFN 后各业务模块直接调用 WBNetworkManager 的接口,这就隔离了业务层与 AFN 的直接交互逻辑,这么做显而易见的好处是解决了上文中提到的第 1 个问题,即使 AFN 如何更新变化(甚至是切换为别的网络框架),业务层代码都不用做出任何调整,从而便于后期的维护。
促使我们进行二次封装 AFN 的主要原因在于业务需求的调整,比如需要监听当前设备的网络状况(2G/3G/4G/Wi-Fi)变化来实时控制当前网络请求的并发量、需要简单实现两个网络请求的优先级或依赖关系、能否在网络层过滤掉异常数据等,从而迫使网络框架做出调整来适应这些业务需求。因此进入了基于 AFN 做二次封装的阶段,设计 WBNetworkManager 并暴露适当的网络请求接口供业务层调用,内部实现以上通用业务需求。
基于如图 4 所示的对 AFN 二次封装后,我们做了以下几方面在网络框架的改进:
网络操作优先级、依赖机制
由于 AFN 现成的网络操作 AFHTTPRequestOperation 是继承自系统 NSOperation 实现的,也就是说每个网络请求操作我们都可以把它添加到自己封装的 NSOperationQueue(操作队列)中来统一管理,从而实现了对每个网络请求操作的优先级、依赖的管理。这对于某些业务场景是十分有用的,比如我们的 App 在首页数据展示之前会同时并发请求十多个服务端接口,然而用户最关心的可能是首页的展示,所以必须控制首页数据请求为高优先级操作。
网络质量监测机制
如图 4 中我们实现的网络状况实时监控模块,每当网络质量发生变化时来改变一些策略。比如 2G 环境下网络超时的设置就不应与 Wi-Fi 下一致,同样在这两种不同的网络环境下,整个 App 的网络请求并发量也不应相同,从而提升 App 的网络体验。
防死锁与长驻机制
由于增加了网络请求的依赖机制,假设 ABC 三个模块可以同时发起网络请求但是 A 需要等待 B 有结果以后才能执行,又或者存在类似的顺序先后关系,就很容易因操作不当发生死锁。所以我们增加了守护线程来防止操作队列不空的情况下发生死锁,同时还能保证在网络调用者生命周期结束或驻留时间超过预设的限制时尽快释放网络资源(主动取消这些操作)。
经过以上讲述的阶段,似乎已经满足了大部分的业务场景,这也符合开篇讲到的业务驱动框架不断改进的说法,那什么又是技术驱动呢?Apple 在 WWDC 2015 上特意讲到了 NSURLSession(iOS 7 开放的新类)在 iOS 9 及以后系统开始支持 HTTP/2。然而此时大部分 App 也都是兼容到 iOS 7 及以上系统,这就意味着 NSURLSession 将被广泛应用,AFN 也是从 3.0 版本开始切换到对 NSURLSession 的支持。
相信 AFN3.0 的更新给那些直接集成的 App 带来不少麻烦,比如弃用了 AFHTTPRequestOperation 类,从而不再支持直接做线程依赖,而使用全新的类及接口命名,因此使用新版 AFN 就无法继续沿用图 4 中的框架设计,与其总被 AFN 的改动牵着走,索性不如研发一套既适合自己业务场景又能对新类支持的框架,我们就开始进入了自主研发网络框架的阶段。回归到技术点上,NSURLSession 的出现到底有哪些优势呢:
其中最显著又实用的莫过于对以快著称的 HTTP/2 的支持,这里就不过多地阐述 HTTP/2 的原理及优势了,做过 SPDY 优化网络的开发者应该都懂得其中的美妙。优势都清楚了,我们又是怎样解决上面提到的 AFN3.0 中的不足呢?
先来说下 NSURLSession 怎样与 NSOperation 结合使用来实现并发、依赖控制的需求。如图 5 所示,封装可以共享 Session 的 SessionManager 管理类,利用继承自系统 NSOperation 的线程操作类来发起不同的 SessionTask(网络请求任务),并加入到操作队列中统一管理,从而就能控制相应的并发与依赖。由于使用共享的 Session,并发的网络数据返回到同一个 Delegate,通过 SessionTask 的唯一标识(taskIdentifier)分发至队列中相应 Operation 来处理数据解析及回调并完成出队。
这里不得不提的一点就是为什么要尽量地共享 Session。我们都知道每次发起一个新的 HTTP 请求需要经历 TCP 的 3 次握手才能开始接收数据,而共享 Session 便是能复用 TCP 的连接,从而节省了重复 3 次握手建立连接的时间。图 6 是我们在列表页请求相同的接口、相同的环境下得到的数据,单从数据上来看虽然有一些提升,但相比 HTTP/2 宣称的数倍还差挺远,当然这里最重要的因素取决于服务端是否完全支持 HTTP/2。
至此,我们的网络框架已完全脱离对 AFN 和其他第三方库的依赖。作为客户端独立的底层服务框架而存在,这既满足了网络方面的业务需求也便于后期的扩展与维护,从我们的经历来说突出的优势主要体现在以下几方面:
58 同城移动端高峰时期 DAU 达千万级,每一个用户都是我们前行的动力,绝不会轻易放弃任何一个可以提升用户体验的技术细节,本文仅从技术角度来讲述我们的网络框架在不同阶段演变改进之路。技术框架其实并无绝对的好坏之分,站在不同的相对角度会有不同的结论,然而技术往往是服务于业务,所以说只有适合你自己业务场景的技术框架才算是最合理的框架。