如果要改变动画播放速度,可以直接在时间线概念状态的播放时间中输入具体的值(  )。 A正确 B错误

本文出自Uber移动架构和框架组负责囚托马斯·阿特曼于2016年在湾区Swift峰会上的演讲分享了使用Swfit重写Uber的好与坏。以下为译文: 

我是托马斯·阿特曼,目前是Uber移动架构和框架组负責人Uber现在的用户量已经达到数百万,这么大的用户量Uber是如何用框架实现的呢? 

Swift与百位工程师的故事 — 原因、架构、经验 

今天我想谈谈┅百多名Uber工程师是如何使用Swift编程语言的在上周三新发布的Rider App主应用程序全部都是用Swift语言重构的。接下来我的分享主要包括三个部分:选择Swift嘚原因、Uber新架构;重构经验 

优步的开端——重构的原因 

这是整个移动团队四年前的样子(指向屏幕显示有三名工程师的照片),就是从那时開始他们着手搭建了我们现在这套老应用的基础。老的应用程序已经稳定使用了四年但由于移动开发团队的指数级的增长,这套架构嘚缺点也逐渐显示出来基于这套老架构想做功能开发也变得越来越困难。由于跟不同团队之间共用了很多ViewController所以每次也需要对其它的代碼进行测试。老架构真正让我们感到崩溃的主要原因是它是由两位工程师写出来的但是目前团队已经发展到了100多人。与此同时那套产品本身的用户量也不大。我们已经在多个城市开始运行产品滑块底部密集的问题也显示出来了,原因就是因为所有的团队都希望在他们所在的城市能够推出新的产品我们也想对Rider App做一套全新的用户体验界面。基于上述的这些问题其实归纳起来也就是目前那套应用的架构問题和用户体验界面的全新设计问题。未来不再是研究老架构然后去解决问题这种形式了而是一切都从头开始。 

2015年做了很多纠正错误工莋试图去完善老的结构,但对Uber的全新设计将会从根本上解决问题,到时会处于一个更安全的阶段从头去重新设计也是最理想的一种解决问题的方式。 

重构架构的目标——稳定可靠并且支持未来发展 

基于这两个重构原因开始了新架构的研发最基本的需求就是满足上述兩个要求,保证四条核心流程的稳定这基本上就意味着崩溃率处于最低级别。 如果您的应用程序没有崩溃但用户仍然停留在某些屏幕仩,显然这问题很重大这会让用户觉得不可靠。 

我们当然也希望新开发的架构能够支持Uber接下来数年的发展就像当时设计这套老架构的時候是为了满足过去这四年发展的想法是一样的。 

为了实现上述的两个目标我们选择了Swift。当时我们认为Swift是更加安全的至少在设想里是嘚,然而实际生活中并没有人去验证这一点 

我们认为编译器中的类型安全性会让问题更早的暴露出来,而不是等到产品上线以后再出现問题 

而我们知道,从现在开始的这四年Swift将会进入到一段黄金发展期,它将会成为苹果公司未来唯一一门大力推广的语言 

从今年年初開始启动的,在二月份的时候我们当时还希望我们所做的事情是正确的,因为有一些工程师在以前的公司就花费了大量的时间去做重构嘚事但最终都以失败告终。为了保证重构能成功挑选出了几位核心工程师,让他们花了5个月的时间去研究老的架构在这5个月的时间內,我们就只干这一件事:架构框架,完成一些基础的工作最终搭建了一套很完美的基础框架,所有人都是以这套基础框架为原型进荇开发 

6月,架构搭建好开始让核心流团队开始使用。核心流打算采用一种新的uberX骑行或者是uberPOOL骑行因此我们增加了20位工程师,花了两个朤的时间去审查新的架构确保我们提出来的东西与之前构建一款新产品的要求是吻合的。事实证明与最开始的产品要求相比,的确遗漏了一些东西比如在视图层,一旦工程师开始进行转换或者做一些复杂的视图操作那么我们必须调整架构以满足他们的需求。但是过叻两个月我们取得了新的进展,我们不再需要对代码库进行大量迁移并且把平台开放给了每一个人,如果他们需要的话也可以移交怹们的功能了。 

新架构叫”Riblets”它是由Router、Interaction、Builder、Presenter、View这几个核心组件构成的,这也是VIPER框架的一种思想我们研究了VIPER、MVVM和MVC,最终提出的方案是在VIPER基础之上增加一些我们自己创新的元素在里面最终目标就是将每个功能模块化,并且每一个模块可以独立的进行测试Riblet框架里的每一个核心组件都有一个协议接口,所以开发者可以把每一个单元单独拿出来对它进行充分的测试。Riblets框架里的每一个模块都会在树里面进行管悝因此没有状态机,取而代之的是一个状态树状态树里面的每一个节点就是一个Riblet,新架构中的核心部分是基于业务逻辑的而不是视圖逻辑,并且所有的业务逻辑都是由本地决定的 


以这张树形图里的“注册”模块为例,并不知道它的父节点是谁但是它所需要的都已經注入进来了,是它的父节点注入了它所需要依赖的东西可能还会有一个监听器正在监听注册流,但是监听器是不知道注册模块位于树嘚哪里所以说,这些模块是完全独立的每一个单独的模块都会做本地决策。再比如从“App”模块开始,它仅仅只负责一个业务模块:“目前系统是否有session令牌”这就是它监听的唯一一件事,如果App模块发现在流里面没有session它就会把路径指向到“Welcome”处;如果它发现了有session,那麼它就会跳过“Welcome”模块直接进入到“Bootstrap”模块。 

之后树形图里面右边的每一个组件都知道系统目前是处于“已登录”状态,它们都会有┅个令牌它们都可以从独立注入中取到session令牌,它们也没必要去关心用户是否已经退出了如果在下面的某一个节点处突然进来了一个网絡电话,并且最终导致了session无效那么App组件就会监听到,它就会通过流被调用然后知道系统目前是没有session的状态,紧接着App组件就会中断Bootstrap树並且最终将流指向Welcome组件。 

这就可以让不同团队之间只关注自己负责的那部分业务而没有必要说每做一步都需要去跟其他团队进行沟通交鋶。每个团队都可以做出自己的本地决策并且依赖关系始终得到满足。 

多个文件里面的多行代码 

开发过程中会产生很多代码每一个模塊之间我们都定义了协议。有些组件会关联一个Riblit同时又关联5个不同的文件,因此在代码库里面会有五千多个文件同时还有五十多万行玳码。此外还有一些核心组件是用的Objective-C这也是完全没问题的。 

学习过程中的经验 

在学习Swift的过程中我们也得到了一些经验。 

很显然Swift是一门哽好的语言也正因为这一点,我们才有了一个很好的开端我们几乎用到了Swift提供的所有的功能。 

Swift的可靠性是它带给我们的第一件惊喜恏像是在框架研发的四个月内,我突然发现在整个研发的过程中我的集成开发工具还有我的应用都没出现崩溃现象,即使是在调试的模式下我问了团队里面的其它成员,他们的回答都是没有出现崩溃而在整个开发过程中,第一次出现崩溃是我们尝试着用了一台32位的机器最终导致在解析JSON时出现了整数溢出。那是整个开发周期中出现的第一次崩溃现象 


Swift的可靠性让我们感到非常的振奋,最终的数据显示絕对无故障率是99.99%这已经很接近100%了。一个应用的第一次运行就几乎是没有出现崩溃这种情况我还从来没遇到过。 

必须考虑的一件事是不能允许其他人无条件对新应用进行解压正因这样,也就不会有99.99%的绝对无故障率了所以我们放了一个linting在里面,从而确保没有人可以在任哬条件下进行解压 

你必须考虑到所有的临界情况,就好比你写了很多if但是没有对应的else,那应用程就有可能出现异常因此在调试阶段必须使用声明,最终上线时需要去掉这样应用程序就很少会出现崩溃。 

现在我们需要说一些糟糕的事情了但如果你能从失败中和逆境Φ得到成长,这也是非常有意义的 

首先,如何进行测试就是一件很困难的事Swift是一门静态语言,因此就没有办法像在Objective-C开发中那样去依靠mock測试框架进行测试由于都是基于协议的形式进行开发,并且协议还是以我们这边为主因此我们必须找出针对这些协议的测试方案。举個例子这个协议是用来为实现类创建的一个接口,这个接口允许你根据一个key进行数据保存也允许你根据这个key进行检索数据,如果你有叻交互器想对一些业务逻辑进行测试,比如当它得到某些输入值的时候是否能够将这些值保存到硬盘当中,那你就必须得有一个实现鍺也必须得有一个模拟这种存储场景的页面,有了这些东西你才能测试哪一个方法被调用了。我们开始手动创建这些模拟开始编写玳码,最终得到不可扩展这个结论 我们不能为多个工程师都提供支持。 

我们所做的就是生成了一个小脚本这个小脚本就是负责把大脚夲给转换成小脚本。虽然它自身也有一些问题但最终我们都解决掉了,无论你在哪个环节想生成测试内容只需引入script/generate-mocks。它将会通过你的源码去协议里面查找带有@CreateMock的那些声明,希望Swift在某种程度上给我们提供属性并为你创建mocks。所以当你通过代码库运行时这份协议最终会變成一个StoringMock,它实现了存储它所做的就是实现协议里面所有共有的方法。如果你想知道这份协议被调用了多少次它还提供了计数功能。咜将会为你实现所有的实际的方法无论何时它都有可能返回一个默认的类型。例如在dataForKey中你有一个可选的NSData,而mock只返回nil因为这是完美的。它符合接口如果要排序测试您的输入,您也可以随时调用dataForKeyHandlers将其设置为关闭,并且可以在测试中测试您从测试中得到正确的输入 

同樣的原理,storageDataForKey返回一个StorageResult它是枚举类型的,默认情况下会返回枚举中的第一个成员测试工作的问题就解决了,并且还可以生成所有的mock,我大概算了一下生成的mock大概有100,000行,这100,000行完全是自动生成的是不需要我们再去手工敲代码的。 

另一件糟糕的事情就是开发工具的问题了我們称之为“无限索引”。我不知道为什么会出现这种情况也许你已经遇到过,就是索引器一直在进行索引不知道为什么,它就是无法唍成索引工作与此同时它带来的负面影响就是CPU使用率高达328%,这样笔记本就会变热在不插入电源的情况下,笔记本大概只可以使用一个半小时这真是一件奇怪的事,由于代码每天都在增长这个问题也变得越来越严重。之前我们并没有遇到过这些问题但是我们一旦超過了了200000或者300000行代码,这个问题就将会变得更加的严重 

此外,IDE开始这样做:(屏幕显示Xcode的视频慢慢键入的字符串)。 这不是我打字慢洏是我已经输入了整个字符串,但是IDE是用SourceKit对每一个关键笔划进行检查它可不管我写的代码是不是正确的,而且此时你也根本没办法打字 

如果你碰到这个问题,不妨做这样(屏幕显示删除Xcode的视频) 您可以换成其他应用,比如AppCode团队里有些人就是使用的AppCode。 也有人是这么做嘚先在AppCode中编写代码,然后复制粘贴到Xcode进行编译这样也不会出现问题,真是太奇怪了当然你也可以改善Nuclide,Nuclide是Facebook的IDE目前还不支持Swift,但需偠完善才能支持 

我们的解决方案是增加更多的框架。 将整套应用程序分解成多个框架每个框架只包含很少的文件,他这样做带来的好處就是所有的一切都变得更快了 因为根据我们解决的经验来看,如果框架里面的东西越多工具出现问题的概率也就越大。 

最开始的时候定义了70还是80个框架,如果想定义更多也不是什么难事 当然了,如果你只想编写代码不需要进行编译,那么也可以关闭索引功能吔有一部分人是这么做的。

再来说说二进制文件的大小问题 任何一款App应用,它的大小必须控制在100M以内如果超出了,那么就必须通过WIFI进荇下载这样就会遇到一些问题。如果你的APP应用中存在结构体应用就会变大,如果列表中存在结构体它们会在堆栈中被创建出来,导致应用变大最开始的时候,我们将模型都设置成了结构体最终编译出的二进制文件好像是80M,这不是我们所希望的 

可选的功能也会增加文件的体积,表面上这些功能你可以选择性使用但是其实你并不知道,编译器已经在后台默默地做了很多事编译器必须去检查这部汾代码,还得去解压等等实际上编译出来了很多东西。 

泛型特化是我们遇到的另一个问题 只要你使用了泛型,如果你希望这些泛型变赽编译器将会对它们进行特化,最终编译后的二进制文件也会变大 

Swift运行时所依赖的那些库文件也会包含到应用程序中,我们对这些库攵件进行了压缩最终实际大小只有4.5MB。 

你可以通过优化设置解决这个问题打开O-whole-module-optimization优化等级,有时可能会将编译文件变小有时也会导致变夶,这就需要你知道哪里的编译比较消耗时间因此我们也做了一个工具,它会将一个单独的符号映射到一个文件最终结合这些文件,伱就可以直观的看见应用程序的文件夹结构以及每一个Swift文件的大小

启动速度是我们开发过程中遇到的另一件棘手事情。如果你看过了苹果全球开发者大会的演讲那么你就会得出这样一个结论 - Swift可以实现更快的启动速度,现在却出现了完全相反的情况通常情况下二进制文件中的动态库的数量将会直接影响在pre-main中启动时间,可pre-main和post-main就是由这两个决定的Pre-main发生在主方法调用之前,如果动态库的数量太大花费的时間也就会更多。 

比如在一台iPhone 6s手机上面,Swift运行时的库需要花费250毫秒才能完成他们的动作这也就意味着在这250毫秒期间,你使用Swift也没办法返囙这是一种懒汉现象。 

我们发现我们所遇到的工具问题是由于创建了更多的框架引起的你框架里的东西越多,那么你的启动速度就越慢 

可以将所有内容重新链接到二进制文件中,这就是我们采用的方案 我们构建了这些框架,并做了后期处理将所有的符号从这些框架中取出,将它们链接到静态二进制文件中这就是我们解决启动速度慢的方案。 

在企业证书方面你也有可能会遇到问题如果你的设备具有企业证书,那么APP时可能需要花费十秒钟的时间去进行初始化加载具体得依赖于证书数量。 

你可以通过重链接降低时间当然你也可鉯通过做其它的一些调整来增加post-main 时间。 

目前我们正在尝试使用DTrace来探测启动序列中的访问符号由于做了重链接,所以保证它们是按照正确順序进行这样就防止在一些老设备当中,不需要将加载大量的页面到内存中但是启动过程中你可以按照需求将某些页面给读到内存中。


令我们感到羞愧的一面 

如果你参加了昨天或者一年前的Swift峰会的话你就能感受到了,在演示的过程中我们遇到了一件真正的麻烦事那僦是编译速度非常的慢,我们的基础应用需要花费15到20分钟才能完成clean工作 

对于这件事情,我们都很担心因此我们去咨询了团队中的每个囚:“这个问题到底有多大”,当时我们是这么问的:”根据以往编程过程中遇到的问题整体思考一下,在优步未来发展的过程中哪一門语言你觉得会更适合于iOS的开发?” 

这是根据根据结果做的统计图几乎是一半一半: 

结果显示即使Swift有出错率、无限索引、编译速度等各种問题,但是他们还是坚持会使用Swift另一半人则选择换回Objective-C。 

因此我们又增加了另外一个问题: 

“如果实现了下面哪一件事或者哪两件事那麼你就会选择Swift,甚至也改变了你对Swift的认知” 

如果仅仅是由于编译速度的问题实际上我们是可以解决的。 

弄清楚原因以后我们做的第一件事就是切换回Swift。尽量不要在代码中使用类型判断我们研究出了一个使用SourceKit开发的脚本,这个脚本可以在后期构建所有类型只需更改代碼,使其具有所有类型信息就可以了 

最后,我们开始组合文件我们发现将我们所有的200个模型组合成一个文件以后,可以将编译时间从1汾35秒减少到只有17秒 所以我们觉得“如果继续将其它的一切都结合成一个文件,那速度岂不是可以更快这真是太有趣了”。 这样做的原洇是因为编译器会对每个独立的文件进行类型检查所以如果您生成了Swift编译器的200个进程,则需要检查所有其他文件的200x因此将所有内容组匼成一个文件可以使其编译的更快。 

全模块的优化正是我们想要做的 它将所有的文件都编译成了一个文件。 全模块优化问题就是优化所以它相当慢。 但是如果添加用户定义的自定义标志SWIFT_WHOLE_MODULE_OPTIMIZATION将其设置为yes,并将优化级别设置为none那么,它将完成全模块的优化而不进行优化,它会超级快 

目前我们最大的框架是基于Core Flow做的,它有900个文件以前需要四分钟才能编译完,现在只需要23秒就可以了只需要花23秒的时间僦能够将最大的库给编译完,所以即使再也不能进行增量编译了我也觉得无所谓了。大多数其他目标的文件少得多速度也会更快。 

如果整个模块的CPU使用率已经优化到30%以下那么你就可以考虑做一些其他事情了。如果使用Objective-C语言进行开发那我们就必须使用Buck。Buck提供了更好的依赖管理可靠的增量编译以及远程编译缓存。它是由Facebook创建如果在编译期间出现问题你可能就会关注它了。我们之前曾分别在objective-c和Android编译过最终我们的清理编译速度提高了4倍,我们的增量编译快了20倍因为它使用了远程编译缓存,所以如果你正在编译多个目标而另一些人巳经在其他机器上编译了该代码,那么它将在远程编译缓存中可用并且只会使用该文件,它不会重新编译任何东西 在Android上,它的速度更赽像6倍快的清理编译时间,而增量版本只是快速的 

这不是Swift,但我们正在努力所以我们一直在为Facebook能够支持Swift而努力,我们现在开始尝试茬生成Xcode项目文件的时候能够添加Swift支持我认为现在这个目标已经几乎或者接近实现了,我们已经在内部开始这么做了现在已经可以根据攵件夹结构是用Buck来生成工程文件了。 

接下来我们正在为实现Buck编译添加Swift支持而努力,这么做的目的就是以后可以使用Buck编译我们的应用程序 最终我们还想要去研究如何将已经添加到Buck的Swift支持整合到Xcode中,如果研究成功的话那么当你打cmd + B,它不会使用Xcode编译而是会使用Buck进行编译。 

洳果使用Buck的话现在6分钟的编译时间,以后可能会减少到的2分钟甚至更短 这将从本质上解决Swift编译时间的问题。 这一切都可以按照Buck repo进行您最终会看到Swift支持也会加进来的。

百度题库旨在为考生提供高效的智能备考服务全面覆盖中小学财会类、建筑工程、职业资格、医卫类、计算机类等领域。拥有优质丰富的学习资料和备考全阶段的高效垺务助您不断前行!

我要回帖

更多关于 时间线概念 的文章

 

随机推荐