前言介绍

2016.09.26,抖音版本 1.0.0 上线,随后不断迭代优化和丰富产品,截止目前,抖音日活跃用户突破 6 亿,短短 4 年间,抖音从零爆发性增长。

快速的业务发展也对技术支撑提出了更高的要求,为了保障敏捷的业务开发,提升跨团队的协同合作效率,提高本地研发和 CI/CD 效率,抖音 iOS App 工程架构在不同的阶段进行了不同的技术方案的改进,满足合理的架构演化,同时又不影响正常的业务迭代速度。

抖音工程架构演进

架构演进的本质是为了提高研发效率,提高代码稳定性和保证代码质量。架构要解决的问题是如何组织代码。

合理的架构设计可以解决大型项目跨团队协作分工和多业务线并行开发的效率问题。抖音工程代码从一开始就采用了组件化思路,依赖管理工具是定制版的 Cocoapods。

以下动画介绍了抖音工程架构经历的四个阶段的演进过程:

‍图1:抖音项目工程架构演进

‍组件化

在大型项目快速发展的过程中,要保证敏捷开发迭代的最大障碍就是快速膨胀的代码体积导致的编译效率问题,依赖关系复杂化问题,以及业务线代码冲突问题。

移动端项目可以类比后端项目中采用的微服务架构,要解决多业务线并行开发、并行测试问题,采用流水线式迭代开发,提高发版、集成、交付、提审、发布效率,结合分治思想技术选型上可以采用组件化的方案。

大部分小型项目,组件化仅仅做到代码分仓,使用 Cocoapods 的来管理组件依赖,就像抖音项目最初的工程形态。

但是对于几百号人、几十个业务线规模的大型项目,需要设计一套合理的组件分层架构,理清组件间依赖关系,需要 CI/CD 工具链支撑组件发版与集成,需要本地研发工具支撑本地代码同步、工程配置、依赖管理和效率优化。

流水线式迭代开发

流水线(pipeline)技术是指在程序执行时多条指令重叠进行操作的一种准并行实现技术,该技术可以充分提高资源的利用率,同时缩短产品的研发周期。对于客户端项目,流水线技术能很大程度满足敏捷开发迭代的节奏。

图2:抖音流水线式迭代发版

抖音工程架构演进

阶段一:抖音原始工程架构(Original architecture of project)

图3:抖音项目原始工程架构图

抖音项目一开始是单体架构+Cocoapods,业务代码、工程配置、资源文件全部放在一个大业务仓库。由 Podfile 文件描述第三方仓库的依赖版本。

图4:抖音项目原始工程架目录结构

阶段二:分离壳工程后的工程架构(After splitting of host shell pod)

图5:拆分壳工程后的工程架构
分离壳工程后,工程配置、部分系统资源、工程主入口被拆分到主宿主壳工程。

Podfile 拆分出版本依赖管理文件 Podfile.seer,由依赖管理平台进行各个版本的容器化管理,业务仓跟随宿主集成发版,打平依赖,解决版本依赖决议耗时问题。

大业务仓中的代码和资源被拆分到各个业务线的仓库下,由 podspec 文件描述内外依赖。业务线仓库增加 ModuleInterface subspec,存放对外接口,采用依赖注入方式实现接口隔离,初步建立接口层。

业务仓库之间规定只能依赖其他业务仓库的 ModuleInterface subspec,通过 lint 进行编译检查。

部分基础能力代码被拆分成基础仓库,跟第三方仓库一样独立发版。本地研发工具支持单仓开发和多仓开发,不参与代码修改的仓库通过二进制的方式进行链接。同时 CI 流程上也支持通过二进制打测试包,提高打包效率。

图6:抖音项目拆分壳工程后目录结构

壳工程

图7:壳工程抽象

为了满足一个工程同时支持多个项目、部分业务线功能复用、部分业务线中台化发展的需求,我们把所有业务线抽象成独立的 Pod,所有业务 Pod 必须通过宿主的壳工程进行集成发版。

壳工程包含了项目依赖的 Pod 信息描述,同时还包括工程的配置、部分系统级别的资源文件、工程主入口代码。基于多份宿主壳工程,一份代码可以打包出抖音、抖音极速版等项目。

同时,基于宿主壳工程,一些业务线可以通过自动化同步生成自己的子壳工程,实现业务线自己的 Example 工程,进行独立开发,比如有语音通话的 Example 工程,有工具的 Example 工程,有直播的 Example 工程等等。

图8:子壳工程配置同步同步

接口层

接口层顾名思义,只提供依赖的抽象接口,所有接口都是 protocol 协议声明。

接口层限制了所有其他依赖,类、枚举、 外部协议都采用前向声明,podspec 上只允许声明对 DI(依赖注入)框架的依赖。接口层满足封装、隔离和组合的原则。

  • 业务层面对外封装了实现代码;

  • 编译层面隔离了组件间依赖传递,减少头文件 import 嵌套提高编译缓存的命中率,对于 swift 业务组件,还能达到减少编译传递的问题;

  • 架构层面声明抽象协议支持接口组合;

  • DI 容器框架同时支持 stateless DI 容器,也支持 stateful DI 容器。

依赖打平

  • 采用 Cocoapods 本身自带的版本依赖决议进行版本分析会消耗大量的时间;

  • Podfile.lock 过于繁琐,可读性很差,难以解决 Podfile.lock 的冲突;

  • 隐式依赖被动/不符合预期地升级,难以确定性地声明所有依赖,防止隐式依赖被升级;

  • 依赖版本在 Podfile/Podfile.lock 重复声明,增加了解决冲突的成本;

  • Podfile.lock 参与依赖版本决议流程比较复杂,会出现不符合预期的情况。

图9:把版本管理和仓库源信息迁移到 Podfile.seer 文件
  • hook 掉 Cocoapods 采用 podfile.lock 进行版本决议的逻辑,采用 Podfile.seer 文件直接描述所有组件的版本信息,打平依赖。

阶段三:单仓多组件工程架构(Multicomponents in single repo)

图10:拆分单仓多组件后的工程架构

采用单仓多组件后,每个业务线仓库支持添加 podspec 增加组件,实现更小粒度的二进制依赖。业务线仓库内划分业务实现层、业务接口层、服务层和基础层,都是通过集成方式发版。

新增的服务层主要存放公共的业务逻辑和通用服务,限制 UI,一是满足业务逻辑复用,二是满足子壳工程最小化二进制依赖。同时服务层的服务接口也达到隔离依赖传递的目的,在不同的宿主上,支持通过改变服务层实现替换后台能力或者底层能力。建立分层间的依赖准入规则,完善 lint 编译链接检查。

图11:单仓多组件目录结构

编译链接完备性校验

  • 编译校验:分开编译各个 subspec,确保每个 subspec 的依赖是正确的(由于 subspec 没有编译隔离)

  • 接口符号校验:校验当前接口组件(ModuleInterface)中符号是否完备的,以保证其他组件单独引用是否能正常使用。如 extern 声明的全局变量。

分层依赖准入规则:

  • 高层依赖低层

  • 实现依赖接口

  • 接口层无依赖

  • 前向声明优先

  • 服务层去"UI"

以下动画展示了业务实现层和服务实现允许依赖的分层:

图12:组件依赖关系示意图动画

阶段四:Example 子壳工程架构(Subshell for bizcomponent in example project)

图13:子壳工程架构

每个业务仓从宿主同步工程配置构建子壳工程。增加 AWELaunchKit 为子壳工程提供运行时的基础能力。通过服务层提供业务间运行时共享的服务能力,满足代码复用和更小二进制依赖。

图14:子壳工程目录结构

AWELaunchKit

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。或直接点击阅读原文查看部门所需岗位!

 点击阅读原文,快来加入我们吧!

抖音 iOS 工程架构演进相关推荐

  1. Alibaba iOS 工程架构腐化治理实践

    " 业务开发遇到环境问题越来越多,严重影响开发效率,有些表面看似打包问题,背后却是工程架构的腐化." 背景 近年来,iOS工程复杂度高的负面影响逐渐暴露,很多同学都受到了iOS打包 ...

  2. 精华阅读第 9 期 |滴滴出行 iOS 客户端架构演进之路

    「架构都是演变出来的,没有最好的架构,只有最合适的架构!」最近,滴滴出行平台产品中心 iOS 技术负责人李贤辉接受了 infoQ 的采访,阐述了滴滴的 iOS 客户端架构模式与演变过程.李贤辉也是移动 ...

  3. 抖音商务团队三面java_腾讯抖音iOS岗位三面面经

    腾讯PCG一面(1h) 1.进程和线程的区别 2.死锁的原因 3.介绍虚拟内存 4.常见排序算法,排序算法稳定的意思,快排的复杂度什么时候退化,基本有序用什么 5.TCP可靠性 6.http+http ...

  4. 抖音iOS最复杂功能的重构之路--播放器交互区重构实践

    背景介绍 本文以抖音中最为复杂的功能,也是最重要的功能之一的交互区为例,和大家分享一下此次重构过程中的思考和方法,主要侧重在架构.结构方面. 交互区简介 交互区是指播放页面中可以操作的区域,简单理解就 ...

  5. 网易严选APP工程架构演进

    一.前言 互联网技术发展到今天,移动互联网仍然是一个重要的战略核心,APP也仍然是绝大多数互联网企业用户获客留存的核心渠道. 根据2021年移动应用趋势报告[1],移动应用程序的受欢迎程度仍在持续增长 ...

  6. html5点赞仿抖音,iOS仿抖音—加载点赞动画效果

    iOS仿抖音短视频 前言 前段时间比较忙,最近终于有时间就继续对仿抖音的demo进行更新,本次更新的主要是抖音上的几种动画,下面先来看下效果图: 抖音 说明 经过观察发现抖音主要要以下几种动画效果: ...

  7. 滴滴android架构演进,滴滴出行iOS客户端架构演进之路

    自从蘑菇街的李忠老师在移动前线群里做了一次关于iOS组件化的分享之后,大家对于iOS客户端的架构非常感兴趣,展开了热烈的讨论.我很认同一句话,架构都是演变出来的,没有最好的架构,只有最合适的架构,刚好 ...

  8. 字节跳动抖音ios客户端开发实习生一面面经

    整个一面过程持续了接近一个小时.面试官是字节跳动校招宣传片中的人物,我对他印象很深刻,因为他是北大法学院的毕业生转行做了程序员. 零.自我介绍 一.问项目 1.在做中国象棋游戏这个项目中遇到的最棘手的 ...

  9. 抖音、美团等大厂千万级用户的Android客户端架构演进之路—

    在移动开发中,对开发者来说不同的人具有不同的能力.就像读一本书一样,一千个读者,有一千个哈姆雷特.但不管怎样,只要你是个软件开发者你就必须学习windows或Linux等操作系统的运行原理.Andro ...

  10. 抖音、美团、微信,吊打面试官-Android中高级面试题

    可扩展性: APP 必须能够在用户的 UV/PV 数量快速增加的情况下,保持软件合理的性能.只有这样在快速的从 0 到 1 的需求迭代中才能后顾无忧. 可定制化: 在同一个软件系统中可能面向的用户群体 ...

最新文章

  1. 在QTP中申明XPath
  2. VisualStudio2008+水晶报表的使用
  3. 马思伟:视频领域是个海洋,可以游泳、冲浪、潜水和远航
  4. tcl mysql_MySQL·TCL语言
  5. 使用before、after伪类制作三角形
  6. idea 使用sonarlint报错解决方案
  7. 2020 年 Flink 最佳学习路线,学习的路上,你,并不孤单
  8. redis设置key的有效期
  9. CVX学习笔记(转载
  10. Redis可以做哪些事儿?
  11. Oracle如何使用PL/SQL调试存储过程
  12. 素数问题练习_HDOJ1262
  13. Unity 3D游戏五:打飞碟
  14. 金蝶KIS旗舰版新建,恢复帐套时提示:ActiveX部件不能创建对象
  15. 数学文化——数论之美
  16. 工作人员做好项目协调服务器,项目团队协作做好三件事
  17. The word 'jsp' is not correctly spelled. Eclipse 拼写检查出错处理办法
  18. Unity UI 框架
  19. 查看java web日志_java web 日志详细
  20. 两位图灵奖得主万字长文:新计算机架构,黄金十年爆发!

热门文章

  1. Recovering BST
  2. python中endswith函数什么意思_Python中endswith()函数的基本使用
  3. nmap工具进行端口扫描
  4. SIGIR'22 | 利用最小化互信息学习反事实推断中的解耦表征
  5. 网站首页导航栏移入移出动画(一)slideDown、slideUp
  6. ubuntu计算机名用户名,修改ubuntu的用户名(注意用户名和主机名的区别)
  7. [毕业生的商业软件开发之路]积累与创新
  8. 900万!!!!!!!!这也太强了吧!!!我的老天!!!!!!!!!!
  9. webservice axis2+spring整合
  10. 霍尼韦尔Granit 1990iSR工业二维码扫描枪