Swift 编码规范
- 基本原则
- 参考资料
- 通用规则
- 格式
- 命名
- 编码风格
- 访问修饰符
- Enum
- Optional
- 属性
- Closure
- Collection
- 错误处理
- Guard
- Self
- 注释
基本原则
本章所含条款是代码规范的基本思想,适用于所有的编程语言。
1. 清晰
- 代码要简单易懂
- 代码要没有歧义
2. 简洁
- 做技术的人不说废话
- 在不违反第一条原则的前提下有效
3. 本地化
- 代码要更像Swift
- 要和Swift的代码风格保持一致
- 要和Swift的编程思想保持一致
- Swift的代码追求接近自然语言
参考资料
- Swift API 设计指南
- raywenderlich的代码规范
- linkedIn的代码规范
- dialect of Markdown
通用规则
- 代码应 简明、清晰。
- 在名字中包含所有需要的单词。
例:考虑一个移除集合中移除指定位置的元素的方法。
Preferred:
public mutating func remove(at position: Index) -> Element
Not Preferred:
public mutating func remove(position: Index) -> Element
employees.remove(x) // 不明确: 我们是在删除 x 吗?
- 删除不需要的单词。名字中的每个单词都应在使用处表明主要信息。
Preferred:
public mutating func remove(member: Element) -> Element?
Not Preferred:
public mutating func removeElement(member: Element) -> Element?
- 移除无用的代码,包括Xcode模板代码和占位内容。有个例外情况,当你的教程希望别人去使用注释代码。只是单纯调用了super方法的应该被删掉,以及空的没用的UIApplicationDelegate方法。
Preferred:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Database.contacts.count
}
Not Preferred:
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return Database.contacts.count
}
- 把warnings当作errors对待。warning包含了许多编写代码的建议,比如说:不用
++
和--
操作符, 不用C风格的for循环, 不用字符串来表示 selector 等。 - 使方法或函数名在调用的时,符合英语语法规范。
x.insert(y, at: z) // “x, insert y at z”
x.subViews(havingColor: y) // “x's subviews having color y”
x.capitalizingNouns() // “x, capitalizing nouns”
- 尽量使用方法和属性,而不是普通函数。普通函数仅适用于一些特定情况:
- 当没有明显的 self:
min(x, y, z)
- 当函数为无约束泛型:
print(x)
- 当函数的写法是公认记法的一部分:
sin(x)
- 不要写过长的if else
- 尽可能地使用 let 来代替 var 。
- 不要使用返回值重载。
- 保持最小导入。比如:当导入
Foundation
能够满足的时候不导入UIKit
- 代码应该是尽可能的自注释。
- 总是使用Swift的原生类型。
- 不要使用分号,除了
for-conditional-increment
Preferred:
let swift = "not a scripting language"
Not Preferred:
let swift = "not a scripting language";
- 使用
weak
,不使用unowned
。使用[weak self]
和guard let strongSelf = self else { return }
idiom.
Preferred
resource.request().onComplete { [weak self] response in
guard let strongSelf = self else {
return
}
let model = strongSelf.updateModel(response)
strongSelf.updateUI(model)
}
Not Preferred
// might crash if self is released before response returns
resource.request().onComplete { [unowned self] response in
let model = self.updateModel(response)
self.updateUI(model)
}
Not Preferred
// deallocate could happen between updating the model and updating UI
resource.request().onComplete { [weak self] response in
let model = self?.updateModel(response)
self?.updateUI(model)
}
- 使用常量、变量的类型推断
Preferred:
let message = "Click the button"
let currentBounds = computeViewBounds()
var names = ["Mic", "Sam", "Christine"]
let maximumWidth: CGFloat = 106.5
Not Preferred:
let message: String = "Click the button"
let currentBounds: CGRect = computeViewBounds()
let names = [String]()
- 如果你的函数需要返回多个参数,那么尽可能地使用 Tuple 来代替 inout 参数。如果你会多次使用某个元组,那么应该使用
typealias
设置别名。如果返回的参数超过三个,那么应该使用结构体或者类来替代。
func pirateName() -> (firstName: String, lastName: String) {
return ("Guybrush", "Threepwood")
}
let name = pirateName()
let firstName = name.firstName
let lastName = name.lastName
- 不要使用 labeled breaks
- 在编写某个方法的时候注意考虑下这个方法是否有可能被复写,如果不可能被复写那么应该使用
final
修饰符。一般而言,加上final
修饰符后会提高编译的效率,所以应该尽可能地使用该修饰符。 - 使用语法糖
Preferred:
var deviceModels: [String]
var employees: [Int: String]
var faxNumber: Int?
Not Preferred:
var deviceModels: Array
var employees: Dictionary
var faxNumber: Optional
- 尽可能地选用命名函数来代替自定义操作符。
格式
- 一个tab键的大小为4个space
- 确保每个文件的最后都以一个新的空行结束。即回车
- 任何一个地方都不要以空格键结尾。通过Xcode可设置 (Xcode->Preferences->Text Editing->Automatically trim trailing whitespace + Including whitespace-only lines).
- 在方法间应该有空的一行。
- 方法花括号和其他花括号(
if
/else
/switch
/while
etc.) 左花括号和在方法体在同一行,右花括号在新的一行。
class SomeClass {
func someMethod() {
if x == y {
/* ... */
} else if x == z {
/* ... */
} else {
/* ... */
}
}
}
- 冒号前面无空格,后面有一个空格。三目运算符
? :
和空的字典[:]
除外。
// specifying type
let pirateViewController: PirateViewController
// dictionary syntax (note that we left-align as opposed to aligning colons)
let ninjaDictionary: [String: AnyObject] = [
"fightLikeDairyFarmer": false,
"disgusting": true
]
// declaring a function
func myFunction(firstArgument: U, secondArgument: T) where T.RelatedType == U {
}
// calling a function
someFunction(someArgument: "Kitten")
// superclasses
class PirateViewController: UIViewController {
}
- 逗号前面无空格,后面有一个空格。
let myArray = [1, 2, 3, 4, 5]
- 在一个二元/三元运算符(如
+
,==
, 或者->
)的前后应该各有一个空格,而在(
的后面 和)
的前面一般不要出现空格。
let myValue = 20 + (30 / 2) * 3
if 1 + 1 == 3 {
fatalError("The universe is broken.")
}
func pancake(with syrup: Syrup) -> Pancake {
/* ... */
}
- 定义和调用多个参数的方法时,方法参数写在同一行,不要换行。
someFunctionWithManyArguments(firstArgument: "Hello, I am a string", secondArgument: resultFromSomeFunction(), thirdArgument: someOtherLocalProperty)
- 不要在控制流逻辑判断的时候加上圆括号
Preferred
if x == y {
}
Not Preferred
if (x == y) {
}
命名
- 不要在Swift中使用前缀(e.g.
YZT
). - 类型名字使用Pascal命名法(如
struct
,enum
,class
,typedef
,associatedtype
, 等)。 - 函数, 方法, 属性, 常量, 变量, 参数, 枚举值, 使用camel命名法。
- 当处理一个缩略词或者通常都大写的特定的词,代码中一般也要全部大写。但是如果该词出现在某个名字的最开头,则使用小写。
let htmlBodyContent: String = "Hello, World!
"
let profileId: Int = 1
class URLFinder {
}
- protocol的名字应该是个名词。举例:
Collection
,WidgetFactory
。Protocols名字应该是以ing,-able或者-ible结尾。比如:Equatable
,Resizing
。 - 变量、参数、关联类型依据作用对其进行命名,而不是基于它们的类型。
Preferred:
var greeting = "Hello"
protocol ViewController {
associatedtype ContentView: View
}
class ProductionLine {
func restock(from supplier: WidgetFactory)
}
Not Preferred:
var string = "Hello"
protocol ViewController {
associatedtype ViewType: View
}
class ProductionLine {
func restock(from widgetFactory: WidgetFactory)
}
- 如果某个关联类型和它的协议联系非常紧密,导致它的协议名就是它自身的名字,那就给关联类型的名字加上Type避免冲突:
protocol Sequence {
associatedtype IteratorType : Iterator
}
- 常量
- 不需要扩展的常量,定义成enum类型,例如
public enum UIBackgroundRefreshStatus: Int {
case restricted
case denied
case available
}
- 需要扩展的常量,并且很多的时候,定义新的常量类型(struct),并且把这些常量写在这个类型里,例如
class NSNotification {
public struct Name {
init(_ rawValue: String)
static let UIApplicationDidBecomeActive: NSNotification.Name
static let UIApplicationDidEnterBackground: NSNotification.Name
}
}
- 对于泛型及相关类型,用一个大写字母或Pascal命名法。如果有冲突,可以添加一个
Type
后缀。常用的范型符号,例如T
,U
,V
,Item
,Element
.
class SomeClass { /* ... */ }
class SomeClass { /* ... */ }
protocol Modelable {
associatedtype Model
}
protocol Sequence {
associatedtype IteratorType: Iterator
}
- 不要使用缩写形式、短名称、单个字母的名字。
- 常量、属性、变量和类型(struct、class)名称通常会包含表示类型的后缀(UI控件常用)
class ConnectionTableViewCell: UITableViewCell {
let personImageView: UIImageView
let popupViewController: UIViewController
}
编码风格
访问修饰符
- 将访问修饰符放在关键字的第一位。
private static let myPrivateNumber: Int
- 一般,不需要编写
internal
访问修饰符关键字,因为它是默认的。 - 尽可能的使用
private
代替fileprivate
. - 如果你打算让类在特定的module之外还可以被继承,则用
open
,反之则用public
。
Struct
- 使用原生的Swift struct 构造器
Preferred:
let bounds = CGRect(x: 40, y: 20, width: 120, height: 80)
let centerPoint = CGPoint(x: 96, y: 42)
Not Preferred:
let bounds = CGRectMake(40, 20, 120, 80)
let centerPoint = CGPointMake(96, 42)
- 使用
CGRect.infinite
,CGRect.null
, 等, 而不是CGRectInfinite
,CGRectNull
Enum
- 在使用枚举类型作为switch的参数的时候,避免引入 default 关键字,而应该将没有使用的情形放到下面然后使用break关键字来避免被执行。
- Swift中默认会在每个
case
的结尾进行break
,因此没必要的时候不需要显式地声明break
关键字。 - 优先使用譬如
case 1, 2, 3:
这样的列表表达式而不是使用fallthrough
关键字。 - 根据 Apple's API Design Guidelines for Swift 3,使用小写驼峰法命名枚举值。
enum Shape {
case rectangle
case square
case rightTriangle
case equilateralTriangle
}
- 尽量不要写出Enum名称
Preferred
imageView.setImageWithURL(url, type: .person)
Not Preferred
imageView.setImageWithURL(url, type: AsyncImageView.Type.person)
Optional
- 不要使用
as!
或者try!
. - 如果你只是需要判断可选项的值是否为
nil
,则显式的检查其是否为nil
, 而不需要使用if let
语法。
Preferred
if someOptional != nil {
}
Not Preferred
if let _ = someOptional {
}
属性
- 如果是定义一个只读的需要经过计算的属性,那么不需要声明 get {}
var computedProperty: String {
if someBool {
return "I'm a mighty pirate!"
}
return "I'm selling these fine leather jackets."
}
- 如果你的方法没有参数,也没有任何副作用,并且返回一些对象或者值,这个时候使用可计算属性代替方法。
- 尽管你可以在 willSet / didSet 以及 set 方法中使用自定义的名称,不过建议还是使用默认的 newValue / oldValue 变量名
var storedProperty: String = "I'm selling these fine leather jackets." {
willSet {
print("will set to \(newValue)")
}
didSet {
print("did set from \(oldValue) to \(storedProperty)")
}
}
var computedProperty: String {
get {
if someBool {
return "I'm a mighty pirate!"
}
return storedProperty
}
set {
storedProperty = newValue
}
}
- 定义一个单例属性:
class PirateManager {
static let shared = PirateManager()
}
Closure
- 如果可能,参数名称必须与开环大括号保持在同一行。
- 尽量使用 trailing closure。
- 链式语法的trailing closures要注意表达清晰,便于上下文阅读。 例如:
let value = numbers.map { $0 * 2 }.filter { $0 % 3 == 0 }.indexOf(90)
let value = numbers
.map {$0 * 2}
.filter {$0 > 50}
.map {$0 + 10}
- 在escaping闭包中直接使用
self
的时候要小心,可能会引起循环引用。
myFunctionWithEscapingClosure() { [weak self] (error) -> Void in
guard let strongSelf = self else {
return
}
strongSelf.doSomething()
}
Collection
- 避免直接使用下标访问数组。如果可能,使用
.first
或.last
等访问器。 - 使用
for item in items
语法,而不是像for i in 0 ..
。 - 不要使用
+ =
或+
运算符来添加单个元素。推荐使用.append()
或.append(contentsOf:)
,因为这些在Swift的当前状态下性能更高(至少对于编译)。 - 变量初始化为空数组和字典的写法:
Preferred:
var names: [String] = []
var lookup: [String: Int] = [:]
Not Preferred:
var names = [String]()
var lookup = [String: Int]()
- 尽可能地使用 map , filter , reduce , forEach 的组合来进行集合的转换等操作,尽量使用Trailing Closure。
Preferred
let stringOfInts = [1, 2, 3].flatMap { "\($0)" }
Not Preferred
var stringOfInts: [String] = []
for integer in [1, 2, 3] {
stringOfInts.append(String(integer))
}
错误处理
- 当结果应该在语义上可能是
nil
,而不是在检索结果时出现错误,返回一个 Optional 而不是 Error。
Guard
- 当guard表达式要退出的时候,通常表达体内应该一行代码结束,例如
return
,throw
,break
,continue
, andfatalError()
。使用defer
去清理代码。 - 使用
guard
来过滤nil - 使用
guard
来当过滤可选项时 - 如果在两个不同的状态之间选择,使用
if
语句而不是guard
。 - 不要使用单行
guard
语句。
Preferred
guard let thingOne = thingOne else {
return
}
Not Preferred
guard let thingOne = thingOne else { return }
Self
- 避免使用
self
,因为Swift没必要通过它去使用property或者调用方法。 - 在初始化方法里,在property和argument之间使用
self
,和在closure expressions的相关property(编译器是必须):
class BoardLocation {
let row: Int, column: Int
init(row: Int, column: Int) {
self.row = row
self.column = column
let closure = {
print(self.row)
}
}
}
注释
- 使用
// MARK:
注释将代码合理分块。 - 使用新的
- parameter
语法替代旧的:param:
语法 (注意小写parameter
而不是Parameter
). 更多注释细节参考 the documentation on Swift Markup 。 - 对于复杂的类,可以描述类的使用并且附带一些简单的例子。Swift的文档注释支持markdown语法。
/**
## Feature Support
This class does some awesome things. It supports:
- Feature 1
- Feature 2
- Feature 3
## Examples
Here is an example use case indented by four spaces because that indicates a
code block:
let myAwesomeThing = MyAwesomeClass()
myAwesomeThing.makeMoney()
## Warnings
There are some things you should be careful of:
1. Thing one
2. Thing two
3. Thing three
*/
class MyAwesomeClass {
/* ... */
}
- 如果注释中提到代码, 使用``修饰
/**
This does something with a `UIViewController`, perchance.
- warning: Make sure that `someValue` is `true` before running this function.
*/
func myFunction() {
/* ... */
}
- 保证文档的注释尽可能的简洁
-
//
都要跟着一个空格。 - 注释要放在单独的行中。
- 使用
// MARK: - whatever
时,要在注释完后留个空行。
class Pirate {
// MARK: - instance properties
private let pirateName: String
// MARK: - initialization
init() {
}
}