我和我的同事Alex Denisov之前就开发者对Xcode使用和iOS开发的体验性问题进行了数次探讨,这篇文章是对我们谈论内容的总结。
对于我们研发出产品最终的性能和Xcode能否提高开发者的工作效率这些问题,由于我和Alex Denisov的观点产生了明显的分歧,所以我非常愿意把我们的观点分享出来和大家交流。
主要有以下几个方面:
开发环境的问题
- Xcode
- project.pbxproj
图形化 VS 语义(“鼠标驱动的开发”与“键盘驱动的开发”)
- Auto Layout
- Xibs/Storyboards
- Focused tests
有诸多问题的集成工具为什么至今还在?
衡量一个集成开发工具的标准很模糊,换言之可能根本没有标准去衡量它:Monoliths are Bad Design… and You Know It。
我们都知道,集成开工具发是事物从出现,成长到走向成熟的必经之路。但某些观点认为集成开发工具有必要分解成不同功能模块的组件,这样每个组件都可以遵循 单一职责 的原则演变并且对系统的其余部分没有影响。按这种角度来说,LLVM就是从GCC集成编译器中分离出来的,Carthage,“分散依赖理论的创始人”反对有更多的像CocoaPods这样的“集成式”工具,模块化的AFNetwork 2也是由集成式的AFNetwork 1中演化而来,这样的例子还有很多很多。
这有个例子,是关于苹果公司如何修复它众多集成工具之一:Storyboards的。我们花了好几年时间才从苹果公司得到的“官方”的功能,将巨大的Stroyboard文件分割成一个一个更小的single-story-foused,像RBStoryboardLink这样的开源库则被引入的更早,在2012年: RBStoryboardLink被废弃了 。
除了上面的Storyboards被优化的例子,仍然有很多集成工具,让我们看看它们中最有争议性的一些。
Xcode
显然Xcode本身就是一个最大的集成工具:它的主要模块包括文字编辑器和界面搭建器,其它一些是和编译设置管理功能相关的,比如苹果账户管理,源代码控制集成等等 为什么Xcode糟透了 。
我们不明白一个巨大的应用做这一切背后的原因,但我们能看到在Xcode7中纯代码文件和xib文件之间的转换从时间上看可能要花10秒以上,也能看到模拟器的挂起,崩溃或者重启(工作一天可能会看到5次以上的黑屏)。我们从不使用源代码控制这个功能,因为它没法和 SourceTree 这样的专业的源代码管理工具相比,甚至是这个工具仅仅是最普通的命令行。
目前,越来越多的人转去使用AppCode--一款更加注重代码编写的集成开发环境。它并没有图形开发的恐惧和其他Xcode内嵌的模块,但却提供了更好的代码分析和重构工具。我相信,使用AppCode的开发者有更高的效率,因为他们可以不用去关心那些不是真正需要的事情,比如 CocoaPods 的支持,我认为这不是一个像AppCode这样“聪明的”IDE所应该有的,有时候看起来为了赶时髦,某些IDE总想让自己具备所有的功能。
project.pbxproj
project.pbxproj是我认为的仅次于Xcode的第二大集成工具。
我打赌如果你曾经有机会把这个文件从头读到尾的话,你一定和我有同样的印象:这个文件不适合人类管理,我会在心里想它的设计就是反人类的。
模棱两可的标识符在project.pbxproj文件中随处可见 (867CFE661BFFDC5E001F85A8 是一个 /* ViewController.m */正如你所知道的),除了有很糟糕的格式外,还有很多东西是我们所无法控制的。想让下面这些东西变得可阅读和可编辑么,纯文本文件是人们很容易理解和维护的:
- Project structure
- Configurations
- Targets
- Schemes
- Build Settings
- Code signing details,
- Run Scripts (yo shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n";)
- Build Phases (yo 74E916613A2758307FB74A44 /* Embed Pods Frameworks */ = {),
我们还没有设法让CMake来位置iOS的项目结构,但是我坚信下一个.pbxproj文件的代替者将会受一些成熟的编译系统的启发,像Make,CMake,Ninja,Gradle等,他们都基于文本文件,更重要的是它们的设计是符合人类的。
下面我们来讨论一些组织我们舍弃古板的.pbxproj结构的问题。
分组vs文件夹
我们根本不需要分组,因为每个分组总是和对应一个真实的文件夹。甚至可以认为分组是反模式化的,如果一个人用分组来设计的项目结构和真实文件夹的结构不同的话,这通常表明他缺乏对项目真实文件结构的理解,而仅仅关心的是表面的逻辑结构。
对这点我早就提到过:我们已经准备开始用CMake来替换掉Xcode iOS的项目架构了,并且我们也准备开始用和LLVM的开发者使用的几乎一样的_CMakeLists.txt_的方式来维护和开发我们的app的项目架构。
我专门去问了我的安卓开发同事,他也确认在安卓开发中没有组的概念,这样当你向项目中添加一个新文件夹,Git的工作树种除了添加那个文件外其他什么也没有,而这一点和Xcode刚好相反:当我们像Xcode中添加文件时,在project.pbxproj中也会记录该文件的入口,这样会导致我们在merge/rebase操作时产生冲突。
xcconfig文件:包装和继承
我们有比xcconfig更好的管理和设置第三方组件,很好地抽象化编译环境的配置的工具吗。
这个问题 是我们在2006年提出的,现在还在这:
自动换行:
我写了一行很长的代码,超出了我的屏幕:我怎样才能将它截断,C的方法不可行。
你可以在编辑器中打开wrapping设置,但是.xcconfig文件对自动换行支持的并不是很好。
自动换行的特性可以让代码更具可读性,并且降低合并代码时潜在的冲突风险(例如:一个像_-Warning-flags_这样的字符串)。
继承 - 如何向xcconfig文件的变量中添加值 :
有人成功的向xcconfig文件的变量中添加过新值吗?
根据其他人对这个问题给出的原因来看,这个值一般不能被继承。
我们建议的做法是为相对应的变量添加命名空间,在xcconfig文件的末尾加入这一句:
merge.xcconfig:
- <span style="font-size: 16px;">OTHER_CFLAGS = $(inherited) $(APP_PLATFORM_CFLAGS) $(APP_PROJECT_CFLAGS) $(APP_TARGET_CFLAGS)<br></span>
另一个方法是利用CocoaPods,它会用自带的生成器生成特定的xcconfig配置文件,在Pods配置文件中加入:
- <span style="font-size: 16px;">// Pods/Target\ Support\ Files/Pods/Pods.debug.xcconfig?<br>HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/AFNetworking" "${PODS_ROOT}/Headers/Public/ObjectiveSugar"<br></span>
也可以用下面的代替:
- <span style="font-size: 16px;">// AFNetworking.xcconfig<br>HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public/AFNetworking"<br>// FinalConfig.xcconfig<br>#include "AFNetworking.xcconfig"<br></span>
图形化 VS 纯代码:鼠标驱动开发 VS 键盘驱动开发
例子胜过大篇幅的理论,它们的顺序是随机的,让我们来猜猜谁是谁:
- 按下Command + U键运行测试用例而不是用鼠标点击小小的红色或绿色图标
- 在Storyboard中用鼠标设置自动布局而不是用 Carthography 这样的框架去计算frame
- 用Makefile或CMake这样的编译工具构建静态库而不是使用Xcode
- 打开终端执行Git操作而不是Xcode自带的源代码管理工具
是的,在项目初期点几下鼠标确实能帮我们快速地解决血多简单的问题,但是随着我们的系统变得越来越复杂,很难再通过鼠标或触摸板来管理它。
我们的观点是:苹果应该把巨大的人力资源投入到语义开发工具的研发中去(测试框架就是一个好例子),而不是研究那些“具备所有功能”的工具,因为鼠标驱动的开发是不可估量的,而语义开发,永恒的面向对象和SOLID原则都能让系统变得更易扩展。
目前看起来像苹果这样偏爱鼠标驱动的开发者,每当有新的UI特性出现时我们更有可能首先在Xcode中得到新的可点击的控件,但与之相对应的API却不帮助我们管理复杂的系统并让两者的区别兼容。让我们再来看一些例子。
常说的自动布局和UI
如果我们看看网络开发,会发现内容(HTML)和样式(CSS)在早期被划分的很明显。内容和样式都可以由纯文本文件管理的特性启发苹果引入了一些基于同样的原理的工具。我不确定有什么特定的原因让苹果的这些工具的概念和样式表的概念如此相似,而那时样式表还没被引入。
现在一些原生的工具可能对复杂的自动布局处理的不是很好:我们既没有图形化的工具为view设置复杂的约束(除非是真正的鼠标点击者)也没有苹果官方研发的细粒度DSL编程语言(这就是为什么有时候看到成吨的 addConstraint: 代码时让我们愿意选择原生工具)。
这就是为什么如此多的开源解决方案好像开始弥补他们的UI对原生语义支持的不足:ComponentKit和其他的自动布局DSL,像Carthography、Snapkit、Parus都是很好的例子。值得一提的是,到目前为止AFAIK还没有试图创建图形化开发工具,因为没人想为了鼠标驱动而补做什么事情。
在我们的文件中不需要Xib,也没有必要在运行时花费时间处理它们。
用图形化工具搭建一个界面和用OC/Swift代码实现同样的效果,两者之间的差距之大主要是因为Storyboard与Xib没办法让我们看到实现的中间过程。我们所能得到的最终产物只有在应用程序运的行时空间中生成的像视图控制器或视图这样的二进制文件。
完全替代方法是生成中间代码的形式类,它基于工厂模式,以便为特定的视图控制器或视图从工厂产生的Xib/Storyboard文件。不仅将允许开发人员在编译过程之前甚至开始运行时观察中间文件,也会消除编译时和运行时实例化的开销。你能想象在应用程序的源文件中没有Xib/Storyboard的二进制文件吗,你能期待应用程序在instruments中有更好性能表现吗?
按这个方向发展最终我们会意识到工厂模式是人性化的,并可能引发一场新的UI编程革命。
XCTest在逐渐丢失测试特征
我使用Cedar测试框架已经有三年了:这个框架 标记规范 。但使用XCTest时,当在标记模式下你想跑一个测试用例或者一个测试类,唯一的办法是用你的鼠标/触摸板点击那个小的绿色/红色图标,着啊佯作不仅为代表着敏捷开发的测试驱动开发流增加了额外的复杂性,而且降低开发效率。
我们可以使用像 - (void)ftest...`这样的规范或者更好的Clang注释来指示XCTest执行我们标记的测试用例。
结论
说实话,如果有人想听的话我愿意分享更多我对苹果的观点。例如,我还没有写的第三大集成工具xcodebuild,可能在第二部分又会有一篇文章来谈论它。
差不多该结束了:除了上边给到的例子之外,我们发现了一些在开发中简单实用的原则:
-
远离集成工具。如果你想仔细的思考并且尽可能精细的划分你的功能模块,可以使用“高内聚,低耦合”的设计原则。
-
鼠标驱动型开发无法实现的功能,而有详细设计类或声明式编程语言在其中的纯文本文件至少有机会做到。