规范文档翻译自 raywenderlich.com 的编码规范
努力让你的代码在没有警告的情况下编译。 这条规则决定了许多风格决策,比如使用 #selector 类型而不是字符串字面量。(更多请阅读 Swift 3 为什么推荐使用 #selector)。
描述性和一致性的命名让软件更易于阅读和理解。使用 API 设计规范 中描述的 Swift 命名规范。 一些关键点包括如下:
在文章中引用方法时,含义明确是至关重要的。尽可能用最简单的形式引用方法。
addTarget。addTarget(_:action:)。addTarget(_: Any?, action: Selector?)。用上面的例子使用 UIGestureRecognizer, 1 是明确的,也是首选的。
专家提示: 你可以用 Xcode 的跳转栏来查看带有参数标签的方法。
Swift 的类自动被包含在模块分配的命名空间中。不应该再添加类似于 RW 的类前缀。如果不同模块的两个命名冲突,可以在类名前添加模块名来消除歧义。无论如何,仅在少数可能引起混淆的情况下指明模块名。
1 | import SomeModule |
当创建自定义代理方法的时候,未命名的第一个参数应该是代理源。 ( UIKit 包含很多这样的例子。)
推荐:
1 | func namePickerView(_ namePickerView: NamePickerView, didSelectName name: String) |
不推荐:
1 | func didSelectName(namePicker: NamePickerViewController, name: String) |
使用上下文推断编译器书写更短更明确的代码。(你也可以阅读 类型推断。)
推荐:
1 | let selector = #selector(viewDidLoad) |
不推荐:
1 | let selector = #selector(ViewController.viewDidLoad) |
一般的类型参数应该是描述性的、大写驼峰法命名。当类名没有富有含义的关系或角色时,使用传统的单个大写字母来命名,例如 T 、 U 或 V。
推荐:
1 | struct Stack<Element> { ... } |
不推荐:
1 | struct Stack<T> { ... } |
使用美式英语拼写来匹配 Apple 的 API。
推荐:
1 | let color = "red" |
不推荐:
1 | let colour = "red" |
用扩展将代码组织为功能逻辑块。每个扩展都应该添加 // MARK: - 注释,以保证代码的结构清晰。
推荐为协议方法加一个单独的扩展,尤其是为一个模型加入协议遵循的时候。这可以让有关联的协议方法被分组在一起,也可以简化用类关联方法向这个类添加协议的指令。
推荐:
1 | class MyViewController: UIViewController { |
不推荐:
1 | class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate { |
因为编译器不允许在派生类中重新声明协议遵循,所以并不总是需要复制基类的扩展组。如果派生类是一个终端类,并且只有少数方法会被覆盖,那么这个原则尤为正确。应由作者自行决定何时保留扩展组- 。
对于 UIKit 中的视图控制器,可考虑将生命周期、自定义存取器和 IBAction 分组在单独的类扩展中。
无用代码(僵尸代码),包括 Xcode 模板代码和占位注释,应该被移除掉。教程或书籍中教用户使用的注释代码除外。
仅实现简单调用父类,但与教程无直接关联的方法应该被移除。这里包括任何为空的或无用的 UIApplicationDelegate 方法。
推荐:
1 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { |
不推荐:
1 | override func didReceiveMemoryWarning() { |
引用最小化。举个例子,引用 Foundation 就足够的情况下不要再引用 UIKit 。
用两个字符缩进比用制表符缩进更节省空间,同时能防止换行。务必在 Xcode 和项目中设置这个偏好,如下所示:
方法大括号和其他大括号( if / else / switch / while 等)总是在和语句相同的行写左括号,而在新行写右括号。
提示:你可以通过选中一些代码(或按 ⌘A 选中全部)然后按 Control-I (或在目录中选择编辑器 -> 结构 -> 重新缩进)的方式来重新缩进代码。一些 Xcode 模板代码会使用 4 个空格的制表符硬编码,这就是一个修正它的好方法。
推荐:
1 | if user.isHappy { |
不推荐:
1 | if user.isHappy |
? :、空字典 [:] 和带有未命名参数 (_:) 的 #selector 语法 .1 | class TestDatabase: Database { |
不推荐:
1 | class TestDatabase : Database { |
长行应该在 70 个字符左右被换行(这里并非硬性限制,可自行调整)。
避免在行结尾的地方附上空白。
在每个文件的结尾处增加一个单独的换行符。
需要的时候,用注释来解释一个特定的代码片段 为什么 做某件事。注释应保持要么是最新的,要么就被删除。
为了避免块注释和代码内联,代码应该尽可能自文档化。 例外:这不含那些注释被用于生成文档的情况 。
请记住,结构体有 值语义。对没有标识的事物应用结构体。一个包含 [a, b, c] 的数组和另一个包含 [a, b, c] 的数组是完全一样的。他们是可以完全互换的。使用第一个数组还是第二个数组都无所谓,因为他们代表着完全相同的事物。这就是为什么数组是结构体。
类有 引用语义。对有标识或有具体生命周期的事物应用类。你需要将人建模为一个类,因为不同两个人对象是两个不同的事物。只是因为两个人拥有相同的名字和生日不意味着他们是同一个人。但是人的生日应该是一个结构体,因为 1950 年 3 月 3 日和任何其它的 1950 年 3 月 3 日日期对象是相同的。日期本身没有标识。
有时,事物应该是结构体但需要遵循 AnyObject,或在历史上已经被建模为类 (NSDate 、 NSSet)。尽可能尝试遵循这些原则。
这是一个风格良好的类定义例子:
1 | class Circle: Shape { |
上面的例子遵循了以下风格规范:
为了简洁,请避免使用 self 关键词,Swift 不需要用它来访问一个对象属性或调用它的方法。
仅在编译器需要时(在 @escaping 闭包或初始化函数中,消除参数与属性的歧义)才使用 self。换句话说,如果不需要 self 就能编译通过,则可以忽略它。
为了简洁,如果一个计算属性是只读的,则可以忽略 get 子句。仅在提供了 set 子句的情况下才需要 get 子句。
推荐:
1 | var diameter: Double { |
不推荐:
1 | var diameter: Double { |
在教程中将类或成员标记为 final 会从主题分散注意力,而且也没必要。 尽管如此,final 的使用有时可以表明你的意图,且值得你这样做。在下面的例子中,Box 有特定的目的,且并不打算在派生类中自定义它。标记为 final 可以使它更清晰。
1 | // 用这个 Box 类将任何一般类型转换为引用类型。 |
在一行中保持较短的方法声明,包括左括号:
1 | func reticulateSplines(spline: [Double]) -> Bool { |
对于签名较长的函数,则需在合适的位置换行,然后在后续的行中加一个额外的换行:
1 | func reticulateSplines(spline: [Double], adjustmentFactor: Double, |
在一行中保持较短的函数使用,像这样:
1 | let success = reticulateSplines(splines) |
如果是包装调用,则需在合适的位置换行,然后在后续的行中加一个额外的换行:
1 | let success = reticulateSplines( |
仅在参数列表最后有个单独的闭包表达式参数时,使用尾随闭包语法。给闭包参数定义一个描述性的命名。
推荐:
1 | UIView.animate(withDuration: 1.0) { |
不推荐:
1 | UIView.animate(withDuration: 1.0, animations: { |
对于上下文清晰的单独表达式闭包,使用隐式返回:
1 | attendeeList.sort { a, b in |
使用尾随闭包的链式方法应该清晰且在上下文中易读。作者将自行抉择空格、换行、命名与匿名参数的使用。举例:
1 | let value = numbers.map { $0 * 2 }.filter { $0 % 3 == 0 }.index(of: 90) |
请尽可能多的使用 Swift 原生类型。 Swift 提供了 Objective-C 桥接,所以当你需要的时候你仍然可以使用全套方法。
推荐:
1 | let width = 120.0 // Double |
不推荐:
1 | let width: NSNumber = 120.0 // NSNumber |
在 Sprite Kit 代码中,使用 CGFloat 可以让你的代码避免太多转换,从而让你的代码更加简洁。
使用 let 关键字来定义常量,使用 var 关键字来定义变量。如果变量的值不会改变,则要使用 let 来代替 var 。
提示: 一个比较好的技巧就是定义所有的东西都使用 let , 当编译器警告的时候再改为 var 。
你可以在一个类型里面去定义常量而不是在类型的实例变量中去使用类型属性。使用 static let 去声明一个类型属性作为常量。用这种方式声明类型属性比声明全局变量更推荐,因为这种方式更能和实例属性区分开。举例:
推荐:
1 | enum Math { |
注意: 使用无大小写枚举的优势,就是它不会被意外的实例化,而只是单纯的作为一个命名空间。
不推荐:
let e = 2.718281828459045235360287 // 污染全局命名空间
let root2 = 1.41421356237309504880168872
let hypotenuse = side * root2 // 什么 root2?
静态方法和类型属性跟全局函数和全局变量的工作原理类似,应当谨慎使用。当功能的作用域是一个特定类型或需要与 Objective-C 交互时,它们非常有用。
在可接受 nil 值的情况下,使用 ? 声明变量和函数返回类型为可选类型。
用 ! 声明的隐式解包类型,仅用于稍后在使用前初始化的实例变量,比如将在 viewDidLoad 中创建子视图。
当访问一个可选值时,如果值仅被访问一次或在链中有许多可选项时,使用可选链:
1 | self.textContainer?.textLabel?.setNeedsDisplay() |
当一次性解包和执行多个操作更方便时,使用可选绑定:
1 | if let textContainer = self.textContainer { |
在命名可选变量和属性时,需避免类似 optionalString 或 maybeView 这样的命名,因为他们的可选性已经体现在类型声明中了。
对于可选绑定,适当时使用原始名称,而不是使用像 unwrappedView 或 actualLabel 这样的名称。
推荐:
1 | var subview: UIView? |
不推荐:
1 | var optionalSubview: UIView? |
在更细粒度地控制对象声明周期时考虑使用延迟初始化。 对于 UIViewController ,延迟初始化视图是非常正确的。你也可以直接调用 { }() 的闭包或调用私有工厂方法。例如:
1 | lazy var locationManager: CLLocationManager = self.makeLocationManager() |
注意:
[unowned self]。
优先选择简洁紧凑的代码,让编译器为单个实例的常量或变量推断类型。类型推断也适合于小(非空)的数组和字典。需要时,请指明特定类型,如 CGFloat 或 Int16。
推荐:
1 | let message = "Click the button" |
不推荐:
1 | let message: String = "Click the button" |
为空数组和空字典使用类型注释。(对于分配给大型、多行文字的数组和字典,使用类型注释。)
推荐:
1 | var names: [String] = [] |
不推荐:
1 | var names = [String]() |
注意:遵循此原则意味着选择描述性命名比之前更重要。
推荐使用类型声明简短的版本,而不是完整的泛型语法。
推荐:
1 | var deviceModels: [String] |
不推荐:
1 | var deviceModels: Array<String> |
不附属于类或类型的自有函数应该被谨慎使用。可能的话,首选方法而不是自由函数。这有助于可读性和易领悟性。
自由函数最适用于它们与任何特定类或实例无关的情况。
推荐:
1 | let sorted = items.mergeSorted() // 容易领悟的 |
不推荐:
1 | let sorted = mergeSort(items) // 难以领悟的 |
代码 (甚至非生产环境、教程演示的代码)都不应该出现循环引用。分析你的对象图并用 weak 和 unowned 来防止强循环引用。或者,使用值类型( struct、enum )来彻底防止循环引用。
使用惯用语法 [weak self] 和 guard let strongSelf = self else { return } 来延长对象的生命周期。 在 self 超出闭包生命周期不明显的地方,[weak self] 更优于 [unowned self]。 明确地延长生命周期优于可选解包。
推荐:
1 | resource.request().onComplete { [weak self] response in |
不推荐:
1 | // 如果在响应返回前 self 被释放,则可能导致崩溃 |
不推荐:
1 | // 内存回收可以发生在更新模型和更新 UI 之间 |
在教程中,完整的访问控制注释会分散主题且是不必要的。然而,适时地使用 private 和 fileprivate 会使代码更加清晰,也会有助于封装。 在合理情况下,private 要优于 fileprivate。 使用扩展可能会要求你使用 fileprivate。
只有需要完整的访问控制规范时,才显式地使用 open 、 public 和 internal。
将访问控制用作前置属性说明符。仅有 static 说明符或诸如 @IBAction 、 @IBOutlet 和 @discardableResult 的属性应该放在访问控制前面。
推荐:
1 | private let message = "Great Scott!" |
不推荐:
1 | fileprivate let message = "Great Scott!" |
优先选择 for 循环的 for-in 格式而不是 while-condition-increment 格式。
推荐:
1 | for _ in 0..<3 { |
不推荐:
1 | var i = 0 |
当使用条件语句编码时,代码的左边距应该是 「黄金」或「快乐」的路径。就是不要嵌套 if 语句。多个返回语句是可以的。guard 语句就是因为这个创建的。
推荐:
1 | func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies { |
不推荐:
1 | func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies { |
当用 guard 或 if let 解包多个可选值时,在可能的情况下使用最下化复合版本嵌套。举例:
推荐:
1 | guard let number1 = number1, |
不推荐:
1 | if let number1 = number1 { |
对于用某些方法退出,防护语句是必要的。一般地,它应该是一行简洁的语句,比如: return 、 throw 、 break 、 continue 和 fatalError()。应该避免大的代码块。如果清理代码被用在多个退出点,则可以考虑用 defer 块来避免清理代码的重复。
在 Swift 中,每条代码语句后面都不需要加分号。只有在你希望在一行中结合多条语句,才需要加分号。
不要在用分号分隔的单行中写多条语句。
推荐:
1 | let swift = "not a scripting language" |
不推荐:
1 | let swift = "not a scripting language"; |
注:Swift 非常不同于 JavaScript。在 JavaScript 中忽略分号 一般被认为不安全。
条件周围的括号是不必要的,应该被忽略。
推荐:
1 | if name == "Hello" { |
1 | if (name == "Hello") { |
在更大的表达式中,可选括号有时可以让代码读起来更清晰。
推荐:
1 | let playerMark = (player == current ? "X" : "O") |
涉及到 Xcode 项目的地方,组织应该被设置为 Ray Wenderlich 并且包 ID 应该被设置为 com.razeware.TutorialName ,其中 TutorialName 是教程项目的名字。
Aufree
WeChat
Comments