Swift with a hundred engineers阅读

原文在这儿
Uber架构总工程师Tuomas Artman谈了iOS端使用Swift重构过程中遇到的问题,以及一些经验成果总结。

Swift with a hundred engineers - 动机,架构和收获

和一百个工程师写Swift代码意味着什么。
选择Swift的动机,架构。简要陈述。
这次大规模重构的收获。重点阐述。

Uber's beginning - 为什么重构

4年前整个移动端团队只有三个工程师,随着团队的扩张和人员的几何级增长,我们开始看到架构的破碎以及特征开发(feature development)将会变得相当困难。由于我们在不同的团队里共享了许多view controllers,我们不得不测试多个代码路径。我们真的开始被老的架构刺痛,因为它是由两个工程师书写的,然而现在我们的团队已经超过百人。同时我们想要重新设计乘客端app的整体用户体验。基于以上这些,我们决定不再修补原有的架构,而是突破原有架构重新开始。

Architectural goals for rewrite - 可靠并且能够支撑Uber的特色

四条可靠的中心流,低崩溃率,不能卡死在某个界面,能够支持Uber未来几年的发展。

我们选择Swift

我们认为编译器的类型安全将会帮助我们更早地捕捉到问题,而不是通过在正式产品使用时崩溃显现出来。
并且我们知道未来四年Swift将会走进巅峰时期,成为苹果公司唯一倚重的语言。

Timeline

从二月开始,我们花了五个月时间,召集了平台团队的核心工程师们,开始关注架构,这五个月没干别的事情;架构基础差不多完成了。
六月,我们认为已经有了一个好的架构,核心流团队开始运行。我们一起花了两个月时间审查这个架构,尽量确保我们提出的架构适合构建产品。结果证明我们遗漏了一些东西。
比如在视图层面,一旦工程师们开始做转场操作和复杂的视图操作,我们就不得不稍微改变这个架构来满足他们的需求。但是两个月后我们感觉架构的基础代码已经稳定,因此我们向所有人开放了这个平台,告诉他们如果想要的话就可以移植他们的功能。
十一月,我们成功发型了新版本。

Uber的架构

我们叫它“Riblets”,意思是:Router, Interaction and Builder and possibly a Presenter and a View。
这些是App的核心部件。有点像VIPER。我们关注了MVVM,VIPER,MVC,最终我们选择了基于VIPER的创新架构。我们想要做的能够划分所有,能够测试所有。因此在一个Riblet里的每一个部件都有协议接口,我们可以单独拎出一个单元并完美地测试它。所有的Riblets被组织起来成为一个树形结构。因此我们没有状态机,我们有一个状态树。每一个方框代表一个Riblet,并且我们架构的核心不是基于视图的,而是基于逻辑的。我们想要所有的业务逻辑决定都是局部的。


uploads-1491903532242-Tuomas+image+1.png

每一块业务逻辑都是独立的,只需要关注自己的问题,单独做决定,不需要关心其他的事情。也许会有其他模块在监听你的业务逻辑,但是你不知道它在哪儿,知道了也没什么用。
比如“App”这个部件,它只关心一件事情:我们是否有一个session token。如果没有就导向“Welcome”部件,然后在某一时间点,有了session token,就会中断“Welcome”部件,转向“Bootstrap”部件。

许多许多文件,许多许多行代码

所有这些创建了许多代码。我们在所有部件之间都有协议。在我们的代码库中有五百个以上的文件,超过五十万行的Swift代码。

关于Swfit的收获

我们学到了一些关于Swift的好的,不好的以及丑陋的东西。

好的地方

我们相信Swift是一个更好的语言,我们使用了Swift的所有语言特色。

可靠性

四个月的架构开发期间,我的IDE和应用程序竟然没有崩溃过!我的团队其他成员也一样。直到整个架构开发完成也没有遇到崩溃的情况,即使是调试模式。第一次崩溃发生在,我们尝试32位设备时,解析JSON数据,发生了整数溢出。这是整个开发过程中第一次遇到崩溃。
我们发布了重新架构的App版本,最终崩溃率在0.01%左右!这对于一个全新的App来说,以前从没有这种情况。
我们确保没人能够无条件地解包任何东西,如果你这么做了,你会被抓住,并且无法提交修改,这奠定了一个良好App的基础。

Android engineers are now more welcome

我们的架构是跨平台的,iOS和Andriod完全一样,特别是如果Andriod工程师如果使用Kotlin语言的话,更容易接受。如果使用Objective-C,我不认为能够这样。

坏的地方

这是从错误中学习的最有趣的地方。

测试很困难

Swift是一个静态语言,因此你不能像OC一样依赖你的mock框架去测试。并且由于所有类都是基于协议的,我们必须找到一些方法测试这些协议。我们必须自己来写mock测试这些协议。
我们写了一个脚本来自动生成mock。只需要敲下“script/generate-mocks”,就能搜索整个源码文件,在协议顶端如果有@CreateMock声明,就会为你创建mock。

工具问题

另一个不好的地方,我们称它“无尽的索引”,你可能见过。索引器一直在运转,代码量越大越明显,伴随着超高的CPU负荷,笔记本会变得很热,如果不插电源,只能使用一个半小时。这时在Xcode里输入会变得特别困难。

因此你能做什么呢

你可以删掉Xcode……然后转向AppCode。或者在AppCode里面编辑代码,然后粘贴到Xcode里进行编译。
我们把应用拆分成多个framework,这样每个framework中文件数量会少点。似乎一个framwork里面文件越多,工具问题就越明显。
因此起初我们定义架构时就已经拥有了多个framework。总共大概七八十个。
或者你也可以禁用indexing,如果你想在没有代码补全的情况下写代码的话。

二进制文件大小(binary size)

下一个不好的地方,二进制文件的大小。打包后每个app差不多都得100M。
这里有几点需要注意:
structs会增加包的大小
如果你在列表里存储了一些结构体,他们会增加你的二进制文件大小。起初我们所有的模型都是结构体,结果二进制文件大小差不多80M,这不太好。
可选类型(Optional)的使用也会增加二进制的大小。编译器必须做许多事情,必须检查,必须解包。因此即使你只使用了一个问号,在二进制文件里会增大许多。
泛型转化是我们遇到的另一个问题。无论何时你使用泛型,如果你想要泛型更快速,编译器会指定他们,并且增加二进制的大小。
并且Swift运行库需要被包含到你的App里。所有人都说那个差不多12-20M。真实的下载尺寸是4.5M,至少我们是这样,因为它们压缩的很好,还没有加密。因此4.5M是我们测量所得的尺寸。

因此你能做什么呢

你或许可以看看优化设置。打开全模块优化,有时会减小包体积,大多数时间会更大。你需要知道这些体积都从哪儿来。

启动速度

下一个不好的事情,启动速度。通常二进制中动态库的数量直接线性影响mian函数之前的启动速度。因此启动速度分为Pre-main和Post-main。Pre-main是main函数执行前发生的事情。如果你有许多动态库,那么会发生许多事情,许多时间被花费掉。
例如,在一台iPhone6s上,Swift运行时库花费250毫秒去做它的事情,意味着250毫秒之内肯定启动不了。真是无奈。
虽然我们拆分framework解决了工具问题,但是越多的framework意味着越长的启动时间。

因此真的你能做什么呢

好吧,你可以重新链接所有东西到你的二进制文件里,我们就是这么做的。我们编译了所有这些framework,然后我们有一个编译后的步骤,提取所有这些framework的标志符,然后在静态库里面链接他们。这样我们解决了启动速度的问题。
在iPhone6上,不这样做我们的应用启动需要4-5秒。你不能依赖Xcode提供的那些工具。
你也会遇到企业PP文件问题。如果你的设备上有企业PP文件,你的应用可能会花费10秒来加载,取决于文件的多少。
你如果重新链接,那么post-main运行时间会增加。

丑陋的地方

编译速度着实恐怖。我们的基础应用,clean built大约需要15-20分钟。

解决编译速度问题

我们尝试在代码里不使用类型推断。
最后我们开始整合文件。我们发现把所有的200多个model整合到一个文件中,使编译时间从1分35秒降到了17秒!据我所知是因为,编译器为每个单独的文件执行类型检查。
全模块优化做了我们想要做的事情。它会一次编译所有文件。
The problem with whole module optimization is that it optimizes, so its pretty slow. But if you add a user-defined custom flag; SWIFT_WHOLE_MODULE_OPTIMIZATION and set it to yes, as well as set the optimization level to none. It will do whole module optimization without optimizing. And it'll be super fast.
这样做以后,我们的编译时间从20分钟降到了6分钟!

Uber is contributing to Facebook's 'Buck' and adding Swift support

通过Buck你可以继续减少编译时间。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,050评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,538评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,673评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,622评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,047评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,974评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,129评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,893评论 0 209
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,654评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,828评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,297评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,619评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,326评论 3 243
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,176评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,975评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,118评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,909评论 2 278

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,644评论 4 59
  • 静态库与动态库的区别 首先来看什么是库,库(Library)说白了就是一段编译好的二进制代码,加上头文件就可以供别...
    吃瓜群众呀阅读 11,628评论 3 42
  • 想家的时候,最常念叨的一首诗,便是李白的《静夜思》: 床前明月光, 疑是地上霜。 举头望明月, 低头思故乡。 在此...
    如何才能瘦成闪电阅读 157评论 0 0