滴滴出行作为国内互联网出行平台的代表,短短几年时间就从一个小小的创业公司成为一线互联网公司,发 展速度令人瞩目,在如此快速的发展之下,滴滴App在架构上又经历了哪些演变?InfoQ记者采访了滴滴出行平台产品中心iOS技术负责人李贤辉,了解了 滴滴iOS客户端的架构演变之路。
受访嘉宾介绍:
李贤辉,滴滴出行平台产品中心iOS技术负责人。多年客户端开发经验,2010年加入百度日文输入法部门,2011年从C/C++的PC端开发转到百度音乐移动端的iOS开发,2013年加入滴滴。
李贤辉:大家好,我是李贤辉,业余时间喜欢踢踢足球和旅行。我在2013年5月加入滴滴,目前是平台产品中心的 iOS 负责人,经历了滴滴的 2.0 到现在的 4.2.5 版本,并且随着滴滴的快速发展和业务增加,团队规模也不断扩大,业务上经历了拆分整合的过程。很幸运能进入一线互联网公司,选择了热门的技术方向,并且跟 随快速增长的创业公司一起成长。
李贤辉:2013年5月,我们启动了滴滴 2.0 ,主要是 UI 交互方式的改变,采用了MVC的方式,代码结构上做到了更清晰。
2014年5月,新增了专车业务线,需要支持出租车、专车2个业务线,启动了滴滴 3.0 的开发;借鉴了游戏里的状态机,把订单中的阶段,例如:出租车的等待抢单、出租车的等待接驾、专车的等待抢单、专车的等待接驾,都当成一种独立的状态,每 个状态机只需要知道可能要导向的状态机,从而做到了相对独立,状态机满足了出租车、专车双业务线的需求。
2015年1月,滴滴开启了多元化,需要快速上线顺风车、代驾、试驾、巴士等多项业务,代码耦合严重,功能开发和协同发版成为痛点;滴滴的技术团队分散在北京、杭州、上海,不在同一个城市,而需要在同一个 App 里进行发布,客观上增加了架构的难度。
2015年6月到年底,滴滴进行代号为 The One 的组件化框架开发,为了实现“代码治理”,可以“分而治之”的目标,在各个业务线各自开发的情况下防止代码大面积腐化,方便未来再做更加细致的架构重构, 采用CocoaPods的方式进行拆分;组件化之后,公共部分拆分为技术组件、公用业务组件,每个业务线是单独组件,如出租车组件、专车组件、快车组件; 通过把不同功能的组件代码拆分到不同的 pod 里,实现了业务线仅依赖于公共就可以迭代开发,改善了功能开发和协同发版。组件化只是做了治理的第一步,后面的架构优化还任重而道远。
李贤辉:目前滴滴的 iOS 架构采用 MVCS 和 MVVM 的架构方式。MVCS 的 S 是 Store 的意思,包括服务器访问接口控制、数据共享、数据缓存的能力,每个 Store 处理一类情况,例如:订单 Store 会包括发单、取消订单、结束订单、评价订单等,常用地址 Store 会包含编辑常用地址、删除常用地址、拉取常用地址等。MVCS 这种方式的思考逻辑是希望做到组件无状态,完全依赖于 Store 所返回的各种状态信息,这种思想是非常类似于 React Native + Redux 的思路。
项目启动时,原计划用 Objective-C / Java 这种原生代码做一个类似于 React Native 的框架,但是工作量过于巨大且不成熟,不适合在 The One 这种量级项目中使用,后来没有开发该框架。采用 MVCS 的同时也采用 MVVM,用 VM 为 Controller 减肥,但VM 要通过 Store 处理数据层逻辑,达到了为 Controller 瘦身的目的。
李贤辉:我们在15年后半年实施了组件化,组件包括技术组件和业务组件,技术组件是可以跨 App 使用的,例如:网络组件(长连接和短连接)、界面导航管理组件(统一界面转场方式,模块间界面转场,通过openURL方式解耦);根据业务功能,已经实 现了支付、登录、消息、定位、广告SDK、数据统计、分享等组件,每个组件是独立的CocoaPods。
组件采用私有 CocoaPods 来实现,并采用了 Local Pods 的方式,可以在本地不提交代码的情况下,组件与调用方实现调试。组件间的页面间跳转支持 openURL 的方式,由 ONERoute 模块进行管理,页面在 +(void)load
方法中完成注册,ONERoute 内部保存一份 URL 与 Class 的对应表,当调用 openURL 时,会查找到对应的类,然后生成对应的实例对象。这种方式可以通过 URL 解耦具体的类名称,方便从 H5 拉起 Native 页面,未来还可以实现流程的可配置化。在设置页面里,还是直接依赖类的方式,避免过度使用 openURL。为了增加安全性,每个页面会设置是否允许外部打开,仅有允许外部打开的页面才可以通过系统的 openURL 方式打开。
李贤辉:挑战如下:
(1) 消息不及时:如乘客发单后,有司机抢单了,需要尽快知道。后来采用了 socket 长连接,使服务端具备主动通知客户端的能力。
(2) 流量过大:我们和服务器交互较多,流量消耗较大。我们现在在用 protobuf 作为数据载体,有效的减少了流量消耗。
(3) 数据易被修改:共享业务数据多,最初没有限制修改数据的权限,出现一些莫名其妙的问题;后来一方面减少共享的业务数据,并且共享数据对外,尽量是只读,如 果某个状态来源于服务器,则只能在模块内部,在收到服务器结果时,修改接口数据,即 MVCS 的 Store 内部可以修改数据。
(4) 共享地图是另外一个挑战,为减少内存占用,业务线之间切换流畅,在滴滴首页只能有一份地图实例。首页作为主页面,包括导航栏、地图、多个业务线子页面。为 了降低对业务线代码的侵入性,业务线首页从系统的 UIViewController 派生,由首页来设置业务线首页的背景色为透明色;业务线首页的 UI 元素,会在业务线内部完成处理;而在地图模块的操作,首页会收到手势,通过把手势传递给地图模块,使地图得到响应;此外,为了使业务线之间的地图元素互不 干扰,每个业务线的都包括一块独立画布,所有地图元素都在独立画布里处理,当切换业务线时,移除原画布上的所有元素,切换回来时,再恢复画布内容。
李贤辉:滴滴采用了 Lua 和 JSPatch 两种热修复技术,由于 JSPatch 更加被苹果官方认可,目前在线上主要使用 JSPatch。我们还在开发一套 JSPatch 发布平台,内部使用者可以通过后台可以下发和管理脚本,并且处理传输安全等部署工作,提供了脚本后台管理、版本管理,并保证传输安全等功能。
李贤辉:iOS 和 Android 团队在 socket 长连接库使用了同一份代码,iOS 和 Android 底层都是 Unix 系列的内核,对于网络操作是一致的,可以使用同一套系统接口,其次,这套代码的稳定性要求很高,功能与系统无关,一份代码可以降低人力消耗,并保证一致 性。对于和系统相关的功能,都采用设计思路一致,iOS 和 Android 分别开发的方式。
李贤辉:滴滴目前还没有用Swift,因为在iOS 8及以下的平台上,使用Swift需要将Swift运行时打包到App,会增大App体积。滴滴的乘客端由于受限于即将超过 100M 的包体积,目前还没有采用 Swift 的计划。滴滴的出租车司机端预计在下一季度会小范围尝试 Swift 。