业界 iOS 组件化与工程化

Tags
iOS
Date
Apr 19, 2021
美团
Hyperloop,让发布简洁高效
Hyperloop 是服务于美团点评客户端的组件发版、持续集成、App 打包构建、资源调度等各个环节的发布调度系统。名称起源于美国 Elon Musk 构想的 Hyperloop 超级高铁 ,象征着现代、简洁、高效。 Hyperloop 提供了一站式的平台,管理着美团点评 iOS 业务的超过 300 个组件和包括美团 iOS 客户端在内的4个App。接入 Hyperloop 系统后,开发同学可以通过 Hyperloop 来管理自己的项目,配置发版和打包所需要的步骤和检查项。开发完成时,用户只需要登录 Hyperloop 进行相关操作,Hyperloop 就会根据项目的配置去调用不同的步骤,上报每个步骤的状态,给出错误日志、状态通知等。 说到发布,我们首先想到的就是持续集成和交付,而说到持续集成和交付,我们又会自然而然地想到 Jenkins。没错,我们之前的所有和发布工程相关的流程都是与 Jenkins 密切相关,其任务的调度、自动化的构建等功能深受我们的喜爱。 可是随着我们的业务爆发式的增长,加之对 Jenkins 的深入使用,一些问题逐渐暴露出来了: 1. 使用和维护成本加大 业务量的增多,导致参与到整个发布流程中的同学也越来越多,而因为 Jenkins 偏向于专业的配置和运维步骤,给我们普通的开发同学带来很多 Jenkins 的使用和维护上的问题,让我们发布工程的同学不得不花大量的时间来进行答疑和维护。 2.
Hyperloop,让发布简洁高效
有赞
用 Git Subtree 在多个 Git 项目间双向同步子项目,附简明使用手册
什么时候需要 Subtree ? 1、当多个项目共用同一坨代码,而这坨代码跟着项目在快速更新的时候 2、把一部分代码迁移出去独立为一个新的 git 仓库,但又希望能够保留这部分代码的历史提交记录。 有赞微商城曾经是一个很大的前后端代码都包含在里面的 Git 项目,为了方便管理我们把前后端代码分离成2个 Git 仓库,进而再作分项目拆分成多个Git 仓库。 于是,就需要有好的方式同步各个项目共用的Css库、JS库、PHP库(他们都是以独立的 Git 仓库的形式存在)。而且由于开发节奏极快,我们需要这些库是 可以在不同项目间双向同步的而不是单向同步。 而且,最好能做到被迁移的这部分代码在新的git仓库里保留原有的历史提交记录。 举个栗子:A项目需要在给某个子项目W里添加一个文件,最方便的方式自然是直接在A项目里改W子项目对应的目录里的代码,然后测试通过后,把这个更改提交到W子项目的 Git仓库里。如果这时候还要先单独更新W子项目的代码然后提交到 Git 服务器,再在A项目里把W子项目的代码更新过来,显然是很麻烦的,更麻烦的是如果发现代码有bug,还得再走一遍这个流程。 Git Submodule:这是Git官方以前的推荐方案 Git Subtree:从 Git 1.5.2 开始,Git 新增并推荐使用这个功能来管理子项目 npm:node package manager,实际上不仅仅是 node 的包管理工具 composer:暂且认为他是php版npm、php版Maven吧 bower:针对浏览器前端的包管理工具(Web sites are made of lots of things - frameworks, libraries, assets, utilities, and rainbows.
用 Git Subtree 在多个 Git 项目间双向同步子项目,附简明使用手册
百度
知乎
知乎 iOS 客户端工程化工具 - Venom
知乎 iOS 客户端从一开始围绕问答社区到目前涵盖 Feed,会员,商业,文章,想法等多个业务线的综合内容生产与消费平台。项目的复杂程度已经在超级 App 的范畴。单周发布与业务并行开发也逐渐变成主流。同时在知乎 iOS 平台,技术选型一直也都比较开(sui)放(yi)。较早了引入了 Swift 进行业务开发,列表引入了需要 OC++ 的 ComponentKit 作为核心引擎。所以在这种多业务方团队,技术形态复杂,组件仓库数量多等场景下,也同样遇到了各种超级 App 团队都面临的一些问题。 问题如下: 当然在思考解决上面这些问题前,知乎 iOS 项目也同样经历过组件化的工作。与众多组件化拆分方案殊途同归,进行了业务划分,主仓库代码清空,业务线及 SDK 进行独立仓库管理。引入基于路由,基于协议声明的组件间通信等机制等,这里就不多赘述了。 核心介绍的项目名称为 Venom,灵感来源于电影《毒液》。Venom 的用户端是一款为开发人员打造 Mac App,应用内置了工程构建需要的全套 Ruby Gem 和 Cocoapods 等其相关构建环境。核心目标是解决工程构建,二进制构建,组件管理,调试工具等一系列开发过程中的繁琐耗时任务。 所以当一台全新的 Mac 电脑希望运行工程时, 只需要 3 步: 安装 Venom For Mac 客户端。 使用 Venom 打开工程点击 Make 按钮。 构建完成点击 XCode
知乎 iOS 客户端工程化工具 - Venom
滴滴
京东
字节跳动
抖音 iOS 工程架构演进
2016.09.26,抖音版本 1.0.0 上线,随后不断迭代优化和丰富产品,截止目前,抖音日活跃用户突破 6 亿,短短 4 年间,抖音从零爆发性增长。 快速的业务发展也对技术支撑提出了更高的要求,为了保障敏捷的业务开发,提升跨团队的协同合作效率,提高本地研发和 CI/CD 效率,抖音 iOS App 工程架构在不同的阶段进行了不同的技术方案的改进,满足合理的架构演化,同时又不影响正常的业务迭代速度。 架构演进的本质是为了提高研发效率,提高代码稳定性和保证代码质量。架构要解决的问题是如何组织代码。 合理的架构设计可以解决大型项目跨团队协作分工和多业务线并行开发的效率问题。抖音工程代码从一开始就采用了组件化思路,依赖管理工具是定制版的 Cocoapods。 以下动画介绍了抖音工程架构经历的四个阶段的演进过程: 在大型项目快速发展的过程中,要保证敏捷开发迭代的最大障碍就是快速膨胀的代码体积导致的编译效率问题,依赖关系复杂化问题,以及业务线代码冲突问题。 移动端项目可以类比后端项目中采用的微服务架构,要解决多业务线并行开发、并行测试问题,采用流水线式迭代开发,提高发版、集成、交付、提审、发布效率,结合分治思想技术选型上可以采用组件化的方案。 大部分小型项目,组件化仅仅做到代码分仓,使用 Cocoapods 的来管理组件依赖,就像抖音项目最初的工程形态。 但是对于几百号人、几十个业务线规模的大型项目,需要设计一套合理的组件分层架构,理清组件间依赖关系,需要 CI/CD 工具链支撑组件发版与集成,需要本地研发工具支撑本地代码同步、工程配置、依赖管理和效率优化。 流水线(pipeline)技术是指在程序执行时多条指令重叠进行操作的一种准并行实现技术,该技术可以充分提高资源的利用率,同时缩短产品的研发周期。对于客户端项目,流水线技术能很大程度满足敏捷开发迭代的节奏。 抖音项目一开始是单体架构+Cocoapods,业务代码、工程配置、资源文件全部放在一个大业务仓库。由 Podfile 文件描述第三方仓库的依赖版本。 图5:拆分壳工程后的工程架构 Podfile 拆分出版本依赖管理文件 Podfile.seer,由依赖管理平台进行各个版本的容器化管理,业务仓跟随宿主集成发版,打平依赖,解决版本依赖决议耗时问题。 大业务仓中的代码和资源被拆分到各个业务线的仓库下,由 podspec 文件描述内外依赖。业务线仓库增加 ModuleInterface subspec,存放对外接口,采用依赖注入方式实现接口隔离,初步建立接口层。 业务仓库之间规定只能依赖其他业务仓库的 ModuleInterface subspec,通过 lint 进行编译检查。 部分基础能力代码被拆分成基础仓库,跟第三方仓库一样独立发版。本地研发工具支持单仓开发和多仓开发,不参与代码修改的仓库通过二进制的方式进行链接。同时 CI 流程上也支持通过二进制打测试包,提高打包效率。 为了满足一个工程同时支持多个项目、部分业务线功能复用、部分业务线中台化发展的需求,我们把所有业务线抽象成独立的 Pod,所有业务 Pod 必须通过宿主的壳工程进行集成发版。 壳工程包含了项目依赖的 Pod 信息描述,同时还包括工程的配置、部分系统级别的资源文件、工程主入口代码。基于多份宿主壳工程,一份代码可以打包出抖音、抖音极速版等项目。 同时,基于宿主壳工程,一些业务线可以通过自动化同步生成自己的子壳工程,实现业务线自己的 Example 工程,进行独立开发,比如有语音通话的 Example 工程,有工具的 Example 工程,有直播的 Example 工程等等。 接口层顾名思义,只提供依赖的抽象接口,所有接口都是 protocol 协议声明。 接口层限制了所有其他依赖,类、枚举、 外部协议都采用前向声明,podspec 上只允许声明对 DI(依赖注入)框架的依赖。接口层满足封装、隔离和组合的原则。 业务层面对外封装了实现代码; 编译层面隔离了组件间依赖传递,减少头文件 import 嵌套提高编译缓存的命中率,对于 swift 业务组件,还能达到减少编译传递的问题; 架构层面声明抽象协议支持接口组合; DI 容器框架同时支持 stateless DI 容器,也支持 stateful DI 容器。 采用 Cocoapods 本身自带的版本依赖决议进行版本分析会消耗大量的时间; Podfile.lock 过于繁琐,可读性很差,难以解决 Podfile.lock 的冲突; 隐式依赖被动/不符合预期地升级,难以确定性地声明所有依赖,防止隐式依赖被升级; 依赖版本在 Podfile/Podfile.lock ...
抖音 iOS 工程架构演进
抖音研发效能建设 - CocoaPods 优化实践
抖音很早就接入 CocoaPods 进行依赖管理了,项目前期抖音只有几十个组件,业务代码也基本在壳工程内,CocoaPods 可以满足业务研发的需求,但是随着业务的不断迭代,代码急剧膨胀,同时抖音工程也在进行架构优化,比如工程组件化改造,组件的数量和复杂度不断增加:组件(Pod)数量增加到 400+ ,子组件(Subspec)数量增加到 1500+ ,部分复杂组件的描述文件(podspec)膨胀到 1000+ 行,这导致了依赖管理流程(主要是 Pod Install)的效率不断下降,同时也导致了 Xcode 检索和构建效率下降。 除了效率下降外,我们也开始遇到一些 CocoaPods 潜在的稳定性问题,比如在 CI/CD 任务并发执行的环境下 Pod Install 出现大量失败,这些问题已经严重影响了我们的研发效率。在超大工程、复杂依赖、快速迭代的背景下,CocoaPods 已经不能很好地支撑我们的研发流程了。 反馈最多就是 Pod Install 慢,经常会有同学反馈 Pod Install 流程慢,涉及到决议流程慢,依赖下载慢、Pods 工程生成慢等 本地 Source 仓库没更新,经常导致找不到 Specification,Pod Install 失败 依赖组件多,循环依赖报错,但是难以找到循环链路 依赖组件多,User 工程复杂度,导致 Pod Install 后 Xcode 工程索引慢,卡顿严重 依赖组件多,工程构建出现不符合预期的失败问题,比如 Arguments Too Long 研发流程上,有部分研发同学本地误清理了 CocoaPods 缓存,导致工程编译或者链接失败 组件拆分后,新添加文件必须 Pod Install 后才可以被其他组件访问,这拖慢了研发效率 我们开始尝试在 0 侵入、不影响现有研发流程的前提下,改造 CocoaPods 做来解决我们遇到的问题,并且取得了一些收益。在介绍我们的优化前,我们会先对 CocoaPods 做一些介绍, 我们以 CocoaPods 1.7.5 为例来做说明依赖管理的核心流程「Pod Install」 我们以一个 MVP 工程「iOSPlayground」为例子来说明,iOSPlayground 工程是怎么组织的: 我们在 Podfile 中为 Target「iOSPlayground」引入 SDWebImage 以及 SDWebImage 的两个 Coder,并声明这些组件的版本约束 然后执行 Pod install 命令 bundle exec pod install,CocoaPods 开始为你构建多依赖的开发环境;整个 Pod Install 流程最核心的就是 ::Pod::Installer 类,Pod Install 命令会初始化并配置 Installer,然后执行 install!
抖音研发效能建设 - CocoaPods 优化实践

Loading Comments...