官方 Swift 风格指南
一定要阅读 Apple 的 API 设计规范。
具体的规范细节和附加说明如下。
本指南已于 2018 年 2 月 14 日针对 Swift 4.0 进行了更新。
1.1 使用 4 个空格代替 1 个 tabs 。
1.2 单行过长会引起阅读不适,每行代码尽量限制在 160 字符内 ( Xcode -> Preferences -> Text Editing -> Page guide at column 设置为160 将会很有帮助)
1.3 确保每个文件末尾都有一个新行。
1.4 确保任何地方都没有尾随的空格( Xcode -> Preferences -> Text Editing -> Automatically trim trailing whitespace 加上 Including whitespace-only lines )。
1.5 不要把左大括号放在新行 — 我们使用 1TBS 风格。
1.6 当为属性、常量、变量、字典的键、函数参数、协议实现或父类书写类型时,不要在冒号 : 前面加上空格。
1.7 通常, , 逗号后面应该有一个空格。
1.8 二元运算符前后都应该有一个空格,比如 + 、 == 或 ->。当然, ( 后面和 ) 前面就不要有空格了。
1.9 我们遵循 Xcode 推荐的缩进风格(即按住 CTRL-I 时,代码不再发生变化)。当声明的函数跨越多行时,推荐使用 Xcode 7.3 默认的语法风格。
1.10 当调用一个多参数函数时,将每个参数放置有额外缩进的单独行中。
1.11 处理大到足以分成多行的隐式数组或字典时,按照方法、if 语句等语法中大括号的风格使用 [ 和 ] 。方法中的闭包也应该用类似的风格处理。
1.12 尽可能避免多行语句,推荐使用局部常量或其他方法。
2.1 在 Swift 中不需要 Objective-C 风格的前缀(比如用 GuybrushThreepwood 代替 LIGuybrushThreepwood)。
2.2 使用 PascalCase 为类型命名(比如 struct 、 enum 、 class 、 typedef 、 associatedtype 等等)。
2.3 对于函数、方法、属性、常量、变量、参数名称、枚举选项,使用 camelCase (首字母小写)。
2.4 实际上,当处理通常全部大写的缩写或其他名称时,代码里用到的任何名称都使用大写。例外情况是,如果这个单词位于以需要用小写开头的名称的开头——在这种情况下,请使用全部小写字母作为首字母缩写词。
2.5 所有与实例无关的常量都应该用 static 修饰。所有这些 static 常量都应该放置在他们的 class 、 struct或 enum 标记过的部分中。 对于有很多常量的类,你应该将拥有类似或相同前缀、后缀 和 / 或者使用情况的常量进行分组。
2.6 对于泛型和关联类型,使用 PascalCase 描述泛型。如果这个单词和它遵循的协议或者它继承的父类冲突,你可以在关联类型或泛型名称后面追加 Type 后缀。
2.7 名称应具有描述性的和明确性。
2.8 不要缩写、使用缩写名称或单字母名称
2.9 如果不明显,请在常量或变量名称中包含类型信息。
2.10 命名函数参数时,请确保函数可以被轻易地阅读并理解每个参数的目的。
2.11 按照 Apple 的 API 设计规范,如果protocol 描述「某事物在做什么」,那么应被命名为名词(比如 Collection )。 如果 protocol 描述「一种能力」,使用后缀 able 、 ible 或 ing(比如 Equatable、ProgressReporting )。如果两种选项都不适用你的用例,你也可以在协议名称后加一个 Protocol 后缀。一些 protocol 的例子如下。
3.1.1 尽可能选择 let 而非 var .
3.1.2 当从一个集合转换到另一个集合时,建议首选 map , filter , reduce 等高阶函数。在使用这些方法时,请确保使用的闭包没有任何副作用。
3.1.3 如果常量或变量的类型可以被推导,则不去主动声明它的类型。
3.1.4 如果一个方法返回多个值,那么推荐使用 inout 修饰的元组类型作为返回值类型 (如果类型不够一目了然,最好使用命名元组来表明你要返回的内容) 。 如果你会多次使用到某个特定的元组,那么可以考虑使用 typealias。 如果你的元组里返回了 3 个及以上的元素,那么使用 struct 或者 class 可能比元组更合适。
3.1.5 在为类声明代理或者协议的时候,要注意循环引用,通常这些属性在声明时要用 weak 修饰。
3.1.6 在逃逸闭包中直接调用 self 的时候,要注意是否会引起循环引用。 - 当可能发生循环引用时尝试使用 capture list :
3.1.7 不要使用labeled breaks.
3.1.8 流程控制语句的条件语句不需要加括弧。
3.1.9 可以使用点语法直接写出枚举值,前面不需要写出枚举类型
3.1.10 在声明类方法的时候不要使用缩写,因为和 enum 相比,推导类的上下文会更难。
3.1.11 除非必要,否则尽量不使用 self. 。
3.1.12 写方法时,要考虑这个方法是否会被重载。如果不会,标记为 final,但请记住,这是为了防止以测试为目的而重载方法。通常, final 方法会将编译时间缩短,所以适时使用它是非常棒的。 但是,在库中应用 final 关键词要非常小心。因为相对于在本地项目中将某些内容改为非 final ,在库中将某些内容改为非 final 可不是小事。
3.1.13 使用诸如 else 、 catch 等后面跟随代码块的语句,将关键字 和代码块放在同一行。强调一下,我们遵循 1TBS 风格 。if / else 和 do / catch 的示例如下。
3.1.14 在定义与类相关的函数或属性而不是定义类的实例变量时时,推荐 static ,而不是 class。如果你特别需要在子类中重载这个函数的功能时,请使用 class 。但是,你应该考虑使用 protocol 来达到这个目的。
3.1.15 如果有一个函数是无参数的、无副作用的而且返回某个对象或值,更推荐使用计算属性来代替它。
3.2.1 如果需要写访问修饰符关键字的话,请将它写在开头。
3.2.2 访问修饰符关键字不应该独占一行,而是将它和其描述的东西放在一行。
3.2.3 通常情况下,访问修饰符关键字默认是 internal ,所以不用写出来。
3.2.4 如果属性需要被单元测试访问,则需要将它标记为 internal ,以便于使用 @testable import ModuleName。如果属性 应该 是私有的,但是出于单元测试的目的将它声明为 internal,一定要添加适当的文档注释来解释这一点。 为了更加简明,你可以使用 - warning: 标记语法,如下所示。
3.2.5 尽可能使用 private 而不是 fileprivate 。
3.2.6 当在 public 和 open 两者之间选择一个时,如果你打算让某些内容在模块外也可以被继承,推荐使用 open ,否则请使用 public。注意,任何 internal 或更高访问权限的内容,都可以通过使用 @testable import 在测试中被继承。所以这不应该成为使用 open 的理由。通常,在涉及到库的时候,更倾向于自由地使用 open 。但是, open 可以轻易地同时改变应用程序中多个模块的内容。当涉及到这类代码库中的模块时,更倾向于保守地使用 open 。
推荐创建自定义运算符。
如果要引入自定义运算符,确保你有一个很好的理由,为什么你想把一个新的运算符引入全局范围,而不是使用其他现有的运算符。
可以重写现有的运算符以支持新类型(特别是 == )。然而,你新定义的必须保存运算符的语义。例如, == 必须是检测是否相等并返回检测结果的布尔值。
3.4.1 当使用具有有限可能性的 switch 语句( enum ),不包括 default 的其他情况。将未处理的情况放置在 default 里,并使用 break 来结束执行。
3.4.2 在 Swift 中由于 switch 的各种情况中默认有 break ,如果不需要,可以省略 break 关键字。
3.4.3 case 和 switch 的声明要按照 Swift 的规范独占一行。
3.4.4 当定义具有关联值的情况时,确保这个值被适当的标记,例如:case hunger(hungerLevel: Int) 而不是 case hunger(Int) 。
3.4.5 更推荐使用 fallthrough 关键字来处理一系列的 cases (例如: case 1, 2, 3: )。
3.4.6 如果您有一个不应该达到的默认情况,最好抛出一个错误(或处理其他类似的方法,如断言)。
3.5.1 使用隐式解包可选类型的唯一机会是使用 @IBOutlet 的时候。在其他情况下,使用非可选或常规可选的属性会更好。是的,有某些情况下,你可以「保证」使用时属性不会为 nil ,但是安全和一致会更好。同样,不要使用强制解包。
3.5.2 不要使用 as! 或 try!.
3.5.3 如果你不打算真正地使用存在可选类型中的值,但需要判断这个值是否为 nil ,显式地检查这个值是不是 nil ,而不是使用 if let 语法。
3.5.4 不要使用 unowned 。你可以将 unowned 视为被隐式解包的 weak 属性的等价物(虽然 unowned 因为完全忽略引用计数而略有性能上的提升)。因为我们不想有隐式解包,所以我们同样也不想要 unowned 属性。
3.5.5 在解包可选类型时,在恰当的地方使用相同名称来命名解包的常量或变量。
在实现协议时,有两种方式组织代码:
使用 // MARK: 注释将协议实现和其他部分的代码隔开。
在同一资源文件中 class/struct 实现代码以外的地方,使用扩展。
记住使用扩展时,无论怎样,扩展中的方法不要被子类重载,这会使测试变麻烦。如果这是一个通用的使用场景,为了一致性使用方法 #1 可能会更好。否则,#2 可以使关系的拆分更清楚。
即使使用方法 #2 ,也要添加 // MARK: 语句,以便在 Xcode 的方法 / 属性 / 类等的列表 UI 中更加易读。
3.7.1 如果创建只读的计算属性,提供不带 get {} 的获取方法。
3.7.2 使用 get {} 、 set {} 、 willSet 和 didSet 时,缩进这些块。
3.7.3 虽然你可以为 willSet/didSet 和 set 自定义新值或旧值的名称,但请使用默认提供的标准标识符 newValue / oldValue 。
3.7.4 你可以按如下方式声明一个单例属性:
3.8.1 如果可以明确参数类型,即可以省略参数类型也可以显示参数类型。你可以根据场景决定是否添加一些说明来提高代码的可读性,或者是省略一些无关紧要的部分。
3.8.2 声明了一个闭包,不需要用括号括起来,除非需要(例如,闭包类型是可选的,或者这个闭包在另一个闭包内)。闭包的参数都是是放在圆括号里,如果用 () 就表示没有参数,用 Void 表示无返回值。
3.8.3 在闭包中尽可能的让参数保持在同一行,避免过多换行。(确保每行小于160个字符)。
3.8.4 如果闭包的含义不太明确可以使用尾随闭包(如果一个方法同时含有成功和失败的两个闭包就不建议使用尾随闭包)。
3.9.1 通常要避免直接用下标的方式访问数组。尽可能使用访问器,比如 .first 或 .last。它们是可选类型并且不会导致崩溃。推荐尽可能地使用 for item in items 语法而不是类似与 for i in 0 ..< items.count 的语法。如果你需要直接用下标访问数组,一定要做适当的边界检查。你可以使用 for (index, value) in items.enumerated() 来一并得到索引和值。
3.9.2 不要使用 += 或 + 操作符来追加或串联到数组。而是使用 .append() 或 .append(contentsOf:),因为在 Swift 当前的状况下它们(至少在编译方面)拥有更高的性能。如果基于其他数组声明数组而且想让它保持不变,使用 let myNewArray = [arr1, arr2].joined(),而不是 let myNewArray = arr1 + arr2。
假设函数 myFunction 应该返回 String,但是,某些时候它会运行错误。在出错时返回nil的情况下,通用的处理方式是让函数返回可选类型 String?。
例如:
相反,在适当的时候,我们应该使用 Swift 的 try/catch 操作来了解失败原因。
你可以使用 struct,如下所示:
用法示例:
有一些例外情况,使用可选类型比使用错误处理更有意义。当返回结果语义上可能是 nil,而不是取回结果时的错误值时,返回可选类型比使用错误处理更有意义。
通常,如果方法可能「失败」,并且返回值为可选类型,失败的原因就不是很明显了,那么方法抛出错误可能会更有意义。
3.11.1 一般情况下,我们推荐在适用的地方使用「尽早返回」的策略 而不是在 if 语句里嵌套代码。在这种使用场景下,使用 guard 语句通常很有用,而且可以提升代码的可读性。
3.11.2 当解包可选类型时,推荐 guard 语句而不是 if 语句来减少代码中嵌套缩进的数量。
3.11.3 在解包类型不复杂,需要在使用 if 还是 guard 之间做抉择时,要记住最重要的是代码的可读性。会有很多可能的情况,例如依赖于两个不同的布尔值、复杂逻辑语句涉及到多个判断等,所以通常使用您的最佳的判断来写出可读且一致的代码。如果你不确定 guard 或 if 哪个更具可读性或者它们看起来同样可读,推荐使用 guard。
3.11.4 如果在两种语句之间做选择,使用 if 语句比使用 guard 语句更有意义。
3.11.5 只有在失败会导致退出当前上下文的情况下,才应该使用 guard。 下面是一个例子,在其中使用两个 if 语句而不是使用两个 guard 语句更有意义—有两个不相互阻塞的无关条件。
3.11.6 通常,我们可能遇到需要使用 guard 语句解包多个可选类型的情况。一般情况下,如果处理每个解包的失败是相同的(例如,return、break、continue、throw 或一些其他的 @noescape),将解包合入一个 guard语句。
3.11.7 不要将 guard 语句写成只有一行。
如果函数比简单的 O(1) 操作负责,通常应该考虑为函数加个文档。因为方法签名的一些信息可能不是那么明显。如果实现方式有任何怪癖,无论在技术上有趣、棘手、不明显等等,都应该被文档化。应该为复杂的类 / 结构体 / 枚举 / 协议和属性添加文档。所有的 public 函数 / 类 / 属性 / 常量 / 结构体 / 枚举 / 协议等也应该被文档化。(如果,他们的签名 / 名称不能使他们含义 / 功能很明显)。
写完文档注释之后,你应该按住 option 键并单击函数 / 属性 / 类等等来确认文档注释被正确地格式化了。
务必查看 Swift 注释标记中提供的全套功能,详见 Apple 的文档。
原则:
4.1.1 160个字符列的限制(和代码的部分一样)。
4.1.2 如果文档注释在一行内,使用( /** */ )。
4.1.3 不要在每一个附加行前面加 *。
4.1.4 使用新的 - parameter 语法而不是旧的 :param: 语法(务必使用小写的 parameter 而并不是 Parameter)。 按住 Option 键并单击你写的方法以确保快速帮助看起来是正确的。
4.1.5 如果你要给方法的参数 / 返回值 / 抛出的异常写文档,即使某些文档最终会有重复,也请将它们都写入文档(这比文档看起来不完整更可取)。有时,如果仅有一个参数需要写文档,在描述中提及它更好一些。
4.1.6 对于复杂的类,请使用一些看起来合适的示例来描述类的用法。记住在 Swift 注释文档中可以使用 markdown 语法。因此,换行符、列表等等都是适用的。
4.1.7 提及代码时,请使用代码提示 - `
4.1.8 写文档注释时,尽可能保持简洁。
4.2.1 始终在 // 后面加个空格。
4.2.2 始终在自己的行中写注释。
4.2.3 使用 // MARK: - 无论是什么 时,在注释后加个空行。