2016.09.26,抖音版本 1.0.0 上线,随后不断迭代优化和丰富产品,截止目前,抖音日活跃用户突破 6 亿,短短 4 年间,抖音从零爆发性增加。shell
快速的业务发展也对技术支撑提出了更高的要求,为了保障敏捷的业务开发,提高跨团队的协同合做效率,提升本地研发和 CI/CD 效率,抖音 iOS App 工程架构在不一样的阶段进行了不一样的技术方案的改进,知足合理的架构演化,同时又不影响正常的业务迭代速度。swift
架构演进的本质是为了提升研发效率,提升代码稳定性和保证代码质量。架构要解决的问题是如何组织代码。后端
合理的架构设计能够解决大型项目跨团队协做分工和多业务线并行开发的效率问题。抖音工程代码从一开始就采用了组件化思路,依赖管理工具是定制版的 Cocoapods。缓存
如下动画介绍了抖音工程架构经历的四个阶段的演进过程:markdown
图1:抖音项目工程架构演进架构
在大型项目快速发展的过程当中,要保证敏捷开发迭代的最大障碍就是快速膨胀的代码体积致使的编译效率问题,依赖关系复杂化问题,以及业务线代码冲突问题。框架
移动端项目能够类比后端项目中采用的微服务架构,要解决多业务线并行开发、并行测试问题,采用流水线式迭代开发,提升发版、集成、交付、提审、发布效率,结合分治思想技术选型上能够采用组件化的方案。less
大部分小型项目,组件化仅仅作到代码分仓,使用 Cocoapods 的来管理组件依赖,就像抖音项目最初的工程形态。分布式
可是对于几百号人、几十个业务线规模的大型项目,须要设计一套合理的组件分层架构,理清组件间依赖关系,须要 CI/CD 工具链支撑组件发版与集成,须要本地研发工具支撑本地代码同步、工程配置、依赖管理和效率优化。ide
流水线(pipeline)技术是指在程序执行时多条指令重叠进行操做的一种准并行实现技术,该技术能够充分提升资源的利用率,同时缩短产品的研发周期。 对于客户端项目,流水线技术能很大程度知足敏捷开发迭代的节奏。
图2:抖音流水线式迭代发版
图3:抖音项目原始工程架构图
抖音项目一开始是单体架构+Cocoapods,业务代码、工程配置、资源文件所有放在一个大业务仓库。由 Podfile 文件描述第三方仓库的依赖版本。
图4:抖音项目原始工程架目录结构
图5:拆分壳工程后的工程架构
分离壳工程后,工程配置、部分系统资源、工程主入口被拆分到主宿主壳工程。
Podfile 拆分出版本依赖管理文件 Podfile.seer,由依赖管理平台进行各个版本的容器化管理,业务仓跟随宿主集成发版,打平依赖,解决版本依赖决议耗时问题。
大业务仓中的代码和资源被拆分到各个业务线的仓库下,由 podspec 文件描述内外依赖。业务线仓库增长 ModuleInterface subspec,存放对外接口,采用依赖注入方式实现接口隔离,初步创建接口层。
业务仓库之间规定只能依赖其余业务仓库的 ModuleInterface subspec,经过 lint 进行编译检查。
部分基础能力代码被拆分红基础仓库,跟第三方仓库同样独立发版。本地研发工具支持单仓开发和多仓开发,不参与代码修改的仓库经过二进制的方式进行连接。同时 CI 流程上也支持经过二进制打测试包,提升打包效率。
图6:抖音项目拆分壳工程后目录结构
图7:壳工程抽象
为了知足一个工程同时支持多个项目、部分业务线功能复用、部分业务线中台化发展的需求,咱们把全部业务线抽象成独立的 Pod,全部业务 Pod 必须经过宿主的壳工程进行集成发版。
壳工程包含了项目依赖的 Pod 信息描述,同时还包括工程的配置、部分系统级别的资源文件、工程主入口代码。基于多份宿主壳工程,一份代码能够打包出抖音、抖音极速版等项目。
同时,基于宿主壳工程,一些业务线能够经过自动化同步生成本身的子壳工程,实现业务线本身的 Example 工程,进行独立开发,好比有语音通话的 Example 工程,有工具的 Example 工程,有直播的 Example 工程等等。
图8:子壳工程配置同步同步
接口层顾名思义,只提供依赖的抽象接口,全部接口都是 protocol 协议声明。
接口层限制了全部其余依赖,类、枚举、 外部协议都采用前向声明,podspec 上只容许声明对 DI(依赖注入)框架的依赖。接口层知足封装、隔离和组合的原则。
采用 Cocoapods 自己自带的版本依赖决议进行版本分析会消耗大量的时间;
Podfile.lock 过于繁琐,可读性不好,难以解决 Podfile.lock 的冲突;
隐式依赖被动/不符合预期地升级,难以肯定性地声明全部依赖,防止隐式依赖被升级;
依赖版本在 Podfile/Podfile.lock 重复声明,增长了解决冲突的成本;
Podfile.lock 参与依赖版本决议流程比较复杂,会出现不符合预期的状况。
图9:把版本管理和仓库源信息迁移到 Podfile.seer 文件
图10:拆分单仓多组件后的工程架构
采用单仓多组件后,每一个业务线仓库支持添加 podspec 增长组件,实现更小粒度的二进制依赖。业务线仓库内划分业务实现层、业务接口层、服务层和基础层,都是经过集成方式发版。
新增的服务层主要存放公共的业务逻辑和通用服务,限制 UI,一是知足业务逻辑复用,二是知足子壳工程最小化二进制依赖。同时服务层的服务接口也达到隔离依赖传递的目的,在不一样的宿主上,支持经过改变服务层实现替换后台能力或者底层能力。创建分层间的依赖准入规则,完善 lint 编译连接检查。
图11:单仓多组件目录结构
如下动画展现了业务实现层和服务实现容许依赖的分层:
图12:组件依赖关系示意图动画
图13:子壳工程架构
每一个业务仓从宿主同步工程配置构建子壳工程。增长 AWELaunchKit 为子壳工程提供运行时的基础能力。经过服务层提供业务间运行时共享的服务能力,知足代码复用和更小二进制依赖。
图14:子壳工程目录结构
AWELaunchKit 框架为宿主和其余子壳工程提供了基础服务的依赖和初始化配置。同时提供了一套启动加载的 BootTasks 管理框架,部分业务涉及启动相关的逻辑能够在业务仓对应的服务层中实现,并经过 BootTasks 管理框架注册到启动加载器里面。
同时框架还提供了一套宿主 UI 入口和自定义入口框架。为了方便测试和调试,也整合了整套测试调试框架。
图15:子壳工程依赖关系
组件之间的依赖除了显式的依赖,还存在不少隐式依赖,代码层面,除了普通的接口依赖,还有宏依赖、枚举依赖、全局变量依赖以及内联函数等的依赖。单仓 lint 进行编译连接完备性检查并不能解决依赖变更对其余二进制的影响。
所以须要借助源码层面的依赖分析,判断当前组件的变动对其余依赖当前组件的二进制是否有影响,在 CI 流程中及时发现并拦截。不然错误的二进制发版,会直接致使整个 CI 研发流程和本地研发都受到影响。
编译优化最高效的方式就是提升缓存的利用率。对于本地研发和 CI 流程,都涉及分布式编译缓存同步。同时经过编译参数优化、依赖优化、hmap 优化也能不一样程度的提升编译效率
对于多业务线并行开发,几百号人的业务开发团队,若是主干分支一旦出现问题,那么解决问题的时间就须要乘上几百倍。所以,须要从编译层面和运行层面都要有足够的机制去保证一个稳定的主干分支,才能保证业务侧的长期稳定性。
大型项目动则千万行的代码,代码间的依赖关系是复杂的网状关系。须要基于代码的语法树模型,从语义中去分析不合理的依赖,并输出治理的方案。
咱们内部自研了源码依赖关系分析平台用于依赖关系分析监控和代码治理,长期监控组件间的依赖度。同时,须要创建依赖健康度模型,从长期演进的角度去监控防止代码的劣化。
图16:spider 组件依赖分析平台
大型项目的组件化工做是一个系统性工程。涉及工程架构的改造、CI/CD 研发工具链的支撑、本地研发工具链的支撑,业务架构的设计优化,须要从各个方面综合考虑成本和收益。
没有最好的架构,只有更好的架构,在架构演进的过程当中,咱们须要充分考虑架构的改动对业务的影响以及能给业务带来的收益。好的架构必定是能帮助业务节省时间,保证质量的。与此同时,咱们在架构改进的过程当中,要保证不能影响业务的正常迭代,因此向前兼容且避免大面积冲突也是很重要的事情。
组件化里面到处都有惊喜,好比一个小小的 hmap 优化,能够很大程度的减小编译耗时,好比一个二进制的压缩和解压的优化,能够很大程度减小 pod install 的总体耗时。
固然这里面也会有不少很棘手的问题,须要经过一些特殊的方案解决,好比针对分布式开发,因为阻塞式发版必然会致使一些不一样分支存在冲突的代码发版后影响主干的稳定性。
因为文章篇幅有限,只能点到即止地介绍当前一些工做成果和思考,各个 Topic 还有一些新的方向在探索,若是你对 iOS 底层原理、架构设计、构建系统、自动化测试有深刻了解,快来加入咱们吧!
咱们是负责抖音客户端基础能力研发和新技术探索的团队。咱们在工程/业务架构,研发工具,编译系统等方向深耕,支撑业务快速迭代的同时,保证超大规模团队的研发效能和工程质量。在性能/稳定性等方面不断探索,努力为全球数亿用户提供最极致的基础体验。
若是你对技术充满热情,欢迎加入抖音基础技术团队,让咱们共建亿级全球化 App。目前咱们在深圳、上海、北京、杭州、均有招聘需求,内推能够联系邮箱:tech@bytedance.com,邮件标题: 姓名-工做年限-抖音-基础技术-iOS/Android。或直接点击连接查看部门所需岗位!
欢迎关注「字节跳动技术团队」
投递简历请联系邮箱:tech@bytedance.com