最佳实践(2):iOS开发篇

772 查看

这份文档就像软件项目一样,如果我们不维护它就会逐渐腐坏。欢迎大家跟我们一起来维护它——只需提交 issue 或者发 pull request 即可!

对其他移动平台感兴趣?也许我们的《安卓开发最佳实践》和《Windows客户端最佳实践》能满足你。

为什么要写这篇文档?

iOS 开发在上手时可能会有些令人生畏。无论是 Objective-C 还是 Swift 在别处都没有广泛的应用,iOS 这个平台几乎对一切都有一套不同的叫法,而尝试把你的代码跑在真机上的过程难免磕磕碰碰。这份持续更新的文档就是来帮你的,无论你是 Cocoa 王国的新手,或是想知道“最佳做法”是什么,都可以一读。下文仅供参考,如果你有理由采取不同的做法,不用顾虑,只管做吧!

上手

Xcode

Xcode 是绝大部分 iOS 开发者选择的 IDE,也是唯一一个苹果官方支持的 IDE。也有一些其他选择,最著名的可能要数 AppCode 了。但除非你已经对 iOS 游刃有余,否则还是用 Xcode 吧。尽管 Xcode 有一些缺点,它现在还算是相当实用的!

要安装 Xcode,只需在 Mac 的 App Score 上下载即可。它自带最新版的 SDK 和 iOS 模拟器,其他版本可以在 Preferences > Downloads 处安装。

建立工程

开始一个新的 iOS 项目时,一个常见的问题是:用代码写界面还是用 Storyboard、xib 画界面。在现有的应用里,这两种做法都占有一席之地。你需要考虑以下几点:

用代码写界面有哪些好处?

  • Storyboard 的 XML 结构很复杂,所以如果用 Storyboard ,合并代码时很容易冲突,比起用代码写的界面要麻烦许多。
  • It’s easier to structure and reuse views in code, thereby keeping your codebase DRY.
  • 用代码写界面时,构建和重用 view 更加方便,因此能保持你的 codebase 遵循DRY 原则
  • 所有的信息都集中在一处。如果用 Interface Builder,你还得到处点开各种检查器,才能找到你要设置的属性。

用 Storyboard 画界面有哪些好处?

  • 对技术不太熟悉的人也可以画 Storyboard,调整颜色、layout 约束,为项目做出直接贡献。不过,要做这些需要工程已经建好,并且也要了解一些基本知识。
  • 开发迭代会更快,因为不需要 build 工程就能预览到做出的改动。
    • 在 Xcode 6 中,在 Storyboard 里终于能看到自定义的字体和 UI 控件样式了。这让你在设计时能更好地了解界面的最终外观。
    • 从 iOS 8 开始,你可以用Size Classes来设计同时支持各种屏幕尺寸的界面,省去了很多重复工作。

gitignore 文件

要为一个项目添加版本控制,最好第一步就弄一个恰当的.gitignore文件。这样一来,不需要的文件(例如用户设置、临时文件等等)就不会进入 repository 了。幸运的是,Github 帮我们同时准备好了 Objective-C 版Swift 版

CocoaPods

如果你准备在工程里引入外部依赖(例如第三方库),CocoaPods提供了快速而便捷的集成方法。安装方法如下:

To get started, move inside your iOS project folder and run

开始的第一步是进入你的工程目录,然后运行

这样会创建一个 Podfile,在这里集中管理所有的依赖。把你的依赖添加到 Profile 里,然后运行

来安装这些库,并且把它们和你自己的工程一起放进一个 workspace 里。在 commit 的时候,一般推荐把依赖在你的 repo 里安装好之后再 commit,最好不要让每个开发者 checkout 之后还要自己跑一遍pod install

要注意,从此以后,打开工程的时候就要打开.xcworkspace文件了,不要再打开.xcproject,否则代码编译不通过。下面这条命令

会把所有的 pod 都更新到 Podfile 允许的最新版本。你可以使用一系列的符号来准确指定你对版本的要求。

工程结构

既然把这些数以百计的源文件都保存在同一目录下,根据工程结构来建立一个目录结构是个好主意。例如,你可以使用以下的结构:

首先,在 Xcode 的 Project Navigator(左边栏)里,把这些目录建立为 group(小小的黄色“文件夹”),建在与工程的同名的 group 下。然后,把每一个 group 与工程路径下实际的文件夹链接起来,方法是选中 group,打开右边栏的 File Inspector,点击小小的灰色文件夹 icon,然后在工程目录下创建一个新的子文件夹,名称与 group 相同。

本地化

从最开始就要把所有的文案放在本地化文件里。这不仅有利于翻译,也能让你更快地找到面向用户的文字。你可以在 build scheme 里添加一个 launch 参数,指定在某种语言下启动 app,例如:

对于更复杂的翻译,比如与名词的数量有关的复数形式(如 “1 person” 对应 “3 people”),你应该使用.stringsdict 格式来替换普通的localizable.strings文件。只要你能习惯这种奇特的语法,你就拥有了一个强大的工具,可以根据需要(例如俄语或阿拉伯语的规则)把名词变为“一个”、“一些”、“少数”和“许多”等复数形式。

更多关于本地化的信息,请参考 2012 年 2 月 HelsinkiOS 大会上的这些幻灯片。其中的大部分演讲至少到 2014 年 10 月为止仍然是不过时的。

常量

把整个 app 范围的常量定义在一个Constants.h文件里,然后在 prefix header 里加入这个文件。

相比使用 #define 定义的预处理宏,使用真正的常量更好:

真正的常量是类型安全的,拥有更明确的作用域,不能在后续的代码中重新定义也不能取消定义,并且在 debugger 中可用。

分支策略

App 发布的时候把 release 代码从原有的分支上隔离出来,并且加上适当的 tag,是很好的做法,对于向公众分发(比如通过 App Store)的 app 这一点尤其重要。同时,涉及到大量 commit 的 feature 应该在独立的分支上完成。git-flow是一个帮助你遵守这些原则的工具。它只是在 Git 的分支和 tag 命令上简单加了一层包装,就可以帮助维护一套适当的分支结构,对于团队协作尤为有用。所有的开发都应该在 feature 对应的分支上完成(小改动在develop分支上),给 release 打上 app 版本的 tag,然后 commit 到 master 分支时只能用下面这条命令:

Common Libraries

常用的库

一般来说,在工程里添加外部依赖要谨慎。当然,眼下某个第三方库能漂亮地解决你的问题,但或许不久之后就陷入了维护的泥淖,最后随着下一版 OS 的发布全线崩溃。另一种情况是,原先只能通过引用外部库来实现的 feature,突然官方 API 也支持了。在设计良好的项目里,把第三方库替换为官方的实现花不了多少功夫,但在将来会大有裨益。永远要优先考虑用苹果官方的框架(也是最好的框架)来解决问题!

因此,这一章有意写得比较简短。下面介绍的第三方库主要用来减少模板代码(例如 Auto Layout)或者用来解决复杂的、需要大量测试的问题,例如计算日期。随着你对 iOS 越来越精通,务必要四处看看它们的源码,熟悉它们所使用的底层框架。你会发现做好这些就能减轻许多重担了。

AFNetworking

大约 99.95% 的 iOS 开发者都使用这个网络库。尽管NSURLSession已经非常强大了,但一旦涉及到实际管理请求队列时,AFNetworking仍然立于不败之地,而现代的 app 基本都会有这个需求。

DateTools

一条常识是,不要自己写日期计算。幸运的是,有 DateTools 这样一个基于 MIT 协议、充分测试过的第三方库,基本能满足所有日期方面的要求。

Auto Layout 相关的库

如果你习惯用代码写 view,你很可能用过这两种诡异的语法——常规的NSLayoutConstraint工厂,以及所谓的可视化语言。前者极其冗长,而后者是基于字符串的,完全躲过了编译检查。

Masonry 的解决方法是:引入自己定义的 DSL 来创建、更新和替换约束。Swift 有一个类似的库 Cartography,是建立在这门语言强大的运算符重载基础上的。保守一些的库有 FLKAutoLayout,它对原生 API 加了一层整洁而不奇异的包装。

架构

  • Model-View-Controller-Store (MVCS)
    • 这是苹果默认的架构(MVC)上增加了一个 Store 层,用来吐出 Model,处理网络请求、缓存等。
    • 每个 Store 暴露给 view controller 的或者是RACSignal,或者是返回值为void、参数带有自定义的 completion block 的方法。
  • Model-View-ViewModel (MVVM)
    • MVVM 是为了解决“巨大的 view controller”而生,它把UIViewController的子类看做 View 层的一部分,用 ViewModel 维护所有的状态来给 ViewController 瘦身。
    • 对于 Cocoa 开发者是一个很新的概念,但是正在引起 越来越多的关注
  • View-Interactor-Presenter-Entity-Routing (VIPER)
    • 相当特别的架构,大型项目可能值得参考,尤其是即使用 MVVM 还是比较凌乱,以及对需要重点考虑可测试性的情况。

“通知” 模型

以下是组件之间互发通知的一些常见手段:

  • Delegation: (一对一) 苹果官方经常用这个模式(有些人认为用得太泛滥了)。主要用于回传,比如从模态框回传数据。
  • Callback blocks: (一对一) 耦合更松,同时能让相关联的代码在一起。并且,消息发出者数量很多时比 delegation 更方便。
  • Notification Center: (一对多) 可能是一个对象给多个观察者发出“通知”时最常用的方法。耦合非常松,甚至可以把通知发到全局,不需要对调度者的引用。
  • Key-Value Observing (KVO): (一对多) 不需要被观测的对象主动“发出通知”,只需要被观测的键(属性)支持 Key-Value Coding (KVC) 。这种模式比较含混,而且标准 API 比较繁复,所以一般不推荐使用。
  • Signals: (一对多) 这是ReactiveCocoa的核心,它允许结合关键内容的链式调用,用这种方法逃离回调深渊(嵌套过多的回调)

Models

要确保你的 model 是不可变的,它们用来把远程 API 的语义和类型转换为 app 适用的语义和类型。Github 的 Mantle 是个不错的选择。

Views

使用 Auto Layout 布局时,要记得在 View 类里加上:

不然,系统可能不会如期调用-updateConstraints,而导致奇怪的 bug。

Controllers

要使用依赖注入,也就是说,应该把 Controller 需要的数据用参数传进来,而不要把所有状态信息都保存在单例里。后者仅当这些状态 的确 是全局的情况下才适用。

网络请求

传统方法:使用自定义回调 block

这样虽可行,但是如果要发起几个链式请求,很容易导致回调深渊。

Reactive 的方法:使用 RAC signal

如果你身陷回调深渊,可以看看ReactiveCocoa (RAC)。这是一个多功能、多用途的库,它可以改变整个 app 的写法。但你也可以仅在适合用它的时候,零散地用一用。

Teehan+Lax以及NSHipster很好地介绍了 RAC 概念(以及整个 FRP 的概念)。

在这里我们可以把 gig 信号与其他信号结合,因此可以在展示 gig 之前做一些修改、过滤等处理。

Assets

使用 Asset catalogs 是管理工程中视觉素材的最好方法。这里既可以添加 iPhone 和 iPad 共用的素材,也可以添加针对特定设备(4寸屏 iPhone,iPhone Retina,iPad 等等)的素材,并且会根据名称来自动提供恰当的素材。教会你的设计师(们)怎么在这里添加并 commit 素材,可以帮你节省许多时间,再也不用把素材从邮件或者别的什么渠道导进代码库里了。同时,这样做也可以让他们即刻看到自己的改动,可以根据需要进行迭代。

使用位图

Asset catalog 只会暴露出一套图片的名字,省略了每张图片实际的文件名。这样,类似button_large@2x.png这类文件的命名空间仅限于 asset 内部,很好地避免了 asset 的命名冲突。然而,命名 asset 时遵循一些原则可以让生活更轻松:

其中的-568h@2x~iphone以及~ipad这些标示符本身不是必需的,但是如果在文件名里加上它们,把文件拖动到 asset 时就能自动落到正确的“格子”上,因此能避免难以察觉的错误拖放。

使用矢量图

你可以把设计师设计的原始的矢量图 (PDFs)放进 asset catalog,让 Xcode 来自动生成位图。这样能减少工程的复杂度(减少文件个数)。

编码风格

命名

Apple 非常注意在 API 中保持命名一致性,有时候有点过于冗长了。做 Cocoa 开发时要遵循Apple的命名规范,这样能让加入项目的新人轻松许多。

以下是几条看了就能用上的基本规则:

动词 开头的方法表示它执行的操作会造成一些影响,但是不返回任何值。

相反的是,以 名词 开头的方法返回一个对象,但不会造成额外的影响。