Uber的工程师在(Swift Summit)会议中分享了其公司花了一年的时间用Swift 重写了整个iOS 应用的经历,下面摘录了项目过程中的故事和心得,希望对大家有所借鉴。
由来
2012年,Uber的三个工程师开发了第一版的iOS应用的基础架构,并使用至今,但随着工程师团队人数的急剧增加,我们遇到了一些急需解决的问题:
- 新功能的开发在已有框架内变得越来越难。
- 不同的团队由于共享太多页面导致测试复杂。
- 已有架构不适合数以百计的工程师同时工作,大团队的烦恼。
- 已有的界面设计灵活性不能满足现在业务需求,每个运营的城市都想定制独有的产品。
- UX团队有重新设计整个应用的打算。
简单来说就是两部分: 已有架构问题和界面交互要重新设计。这些导致了我们放弃在已有基础上打补丁,从零开始做一个全新的项目。
目标
- 架构有足够的可扩展性,可以应对Uber将来的业务发展和变化。
- 核心流程的稳定可靠性,众所周知,一个经常崩溃的应用会让用户失望。
选择Swift
更安全,Swift作为一种类型安全语言虽然没有公司在产品中验证如何安全,但Swift社区一再声明如此,毕竟安全的大旗无往不利。
为了将来。可以预见Swift在未来的时间里是是苹果在iOS平台上唯一主推的语言。再见,Objective-C!
项目周期
整个重写项目持续了将近一年的时间,年初的五个月里,架构团队和框架团队全身心地构造基础相关的架构和框架等相关基础,我们团队里有一些工程师在前家公司经历过重写项目的经历,这次不能重蹈覆辙。
6月份的时候,当基础架构框架准备完毕的时候,我们邀请核心流程团队来验证框架是否如预期满足需求,这时团队成员多了20人。尝试过程中的确发现了一些原先遗漏的问题,如当工程师做场景切换的时候,整个视图的操作变得出乎意料的复杂,我们不得不调整已有框架来满足需求。经过2个月合作,我们认为整个平台已经可以对所有开放,其他业务线团队可以放心迁移到全新的平台中。
我们计划发布的时间是11月份,在Uber公司内部每个项目团队之间非常独立,我们没有统一要求所有团队什么时候开始集成到新的平台,但11月是截止日期,最终有的团队用了整整3个月迁移项目并升级,而有的团队在发布1周前才开始集成,幸运的是我们如计划一样在11月成功发布了新一版应用。
架构
在设计应用架构的时候,团队参考了眼下比较流行的框架 MVC,MVVM 和 VIPER,为了确保足够模块化和代码测试覆盖率,最终选择在VIPER架构的基础上进行定制。其中每个模块都暴露相应协议接口,这样确保每个模块粒度够小和充分测试。
在切割模块化的时候,Uber选择了基于业务逻辑,而不是基于视图,这样每个独立的业务模块可以任意的本地化和定制化。
以上述架构中signup模块为例,signup模块不知道其父节点,该模块在需要的时候会被注入进来,且其父节点会提供所需要的数据,子模块不用关心自己所在的位置,只需完成自己的职责即可,这样保证了子节点的独立性和自主性。
再以应用启动为例,‘App’组件只关心是否已经获取会话令牌,可以用观察者模式监听会话令牌的状态,如果发现没有会话令牌,这是路径切换到‘Welcome’组件,否则将销毁‘Welcome’组件跳转到‘Bootstrap’组件。而上图中右半部分是在用户登录后的状态下运行,每个组件只需使用父节点注入的会话令牌,不用关心用户是否已经退出。‘App’组件会注视这一切的变化,当发现会话令牌过期则会销毁‘Bootstrap’组件群,且回到‘Welcome’组件。
上述架构可以保证多个子团队同时工作在各自的项目中,并不需要太多依赖,且每个子团队在已知模块依赖的前提下做出适合子团队的选择。
代码量
重构以后,新项目有5000多个文件,Swift代码量多达50万行, 不过项目依然保留Objective-C的代码组件。
Swift的苦与乐
正面: Swift的确是一个比Objective-C更好的语言:
-
可靠性,在重写项目的将近一年的时间内,团队成员很少遇到应用崩溃的场景,即使是运行在Debug模式下,连笨重的Xcode也变的稳定起来。根据统计数据新版应用接近零崩溃 - 99.99%的稳定性! 这里有个避免崩溃秘诀是不要乱用强制拆包(!), 推荐使用有条件拆包。还有在调试环境中使用断言(assertion)。
-
Android工程师的福利:Swift对已Android工程师,特别是熟悉使用Kotlin的。这里我们的架构设计是针对Android和iOS多平台的,架构一致,命名一致,工作方式一致。这些都是Objective-C不能提供的。
负面:从错误中汲取教训,请多留意这一部分。
- 测试更难:由于Swift是静态语言,一些Objective-C中模拟对象的策略不再适合。
- 工具问题:当代码达到20万行的时候,不时遇到CPU消耗暴涨和电脑过热奇怪问题,Xcode作为IDE有时候没有代码提示,这里可以在AppCode里完成代码,然后复制粘贴到Xcode里运行,也可直接使用AppCode。当一个框架的代码量较多时,有可能引起无限索引的问题,这时可以考虑拆分为多个较小的组件,Uber项目大概有70-80个可复用组件。
- 二进制文件大小:结构体,泛型和可选值的广泛使用会增加应用编译后的文件大小,还有4.5M大小的Swift运营库会包括在发布的应用中。在设置优化编译的时候,可以选择打开whole module optimization,这样有可能减少编译后的二进制文件,但这也可能增加大小,这时候需要分析下文件大小变化的细节并作出相应调整,团队为此实现了一个内部工具和检测文件大小的变化。
- 启动速度:由于Uber项目中引用多个Swift动态库,导致启动速度较慢,如Swift运行库在iPhone6S上需要250ms准备,这期间是不能使用Swift的。关于多个动态库的问题,建议重新链接这些库。引用下原话:
you can relink everything back into your binary and that's what we did. So we built all these frameworks and then we have a post-build step that takes all the symbols out of these frameworks and links them together in your static binary and that's how we get away with the startup speed that we had.
工具DTrace可以帮助在启动时检测链接库的顺序。
失望
- Swift的编译/索引速度和语言自身问题依然困扰着很多开发者,让很多人局限在Objective-C,不愿迈出那一步,期待Swift社区在未来改变这些问题。
- 在解决编译速度太慢的问题上,我们发现如果把多个模型合并成一个文件,可以大大提高编译速度,原来Swift编译器会对每个文件做类型检测。偶然发现AirBnb也有类似的问题,我们不是孤单的路人啊。后来还是使用了上面提到的办法,在编译设置里手动添加SWIFT_WHOLE_MODULE_OPTIMIZATION并把值设置为Yes即可。我们的核心流程库由原来的4分钟提速到23秒,其实该库只有900个文件。
- Buck:Buck作为facebook在提高编译速度方面的开源项目,虽然还不支持Swift,但Uber在这方面的尝试,将来会反馈给开源社区。
干货
- 多注意编译速度。
- 多注意二进制文件大小。
- 确保单元测试的顺利进行。
- 如果团队较大建议分组。
- 如果你还没有遇到类似问题,可能是团队规模太小,随着团队人数增加,你也许会遇到类似问题,但不用担心,那时候有更多的人来解决相应问题。
有兴趣的可以看看英文的文字记录。
更多
请关注豆志昂扬微信公众号获取更多内容:
- 直接添加公众号豆志昂扬;
- 微信扫描下图二维码;
网友评论