Swift 编码规范
正确性
尽量使代码在没有警告下编译
命名
描述性和一致性的命名会使代码更新易读易懂,使用Swift
官方命名约定中描述的 API Design Guidelines。现总结常用几点:
- 避免不常用的缩写
- 命名清晰比简洁重要
- 使用驼峰命名方法
- 类、协议、枚举不用像
Objectice-c
那样添加前缀
类前缀
Swift 自动以模块名称作为命名空间,不需要给类添加前缀,例如:RW。如果相同的两个类在不同的命名空间下,可以增加模块名字作为前缀。
1 | import SomeModule |
Delegate
在创建自定义的delegate
方法时,第一个参数不带名字的参数应该是delegate
原来的对象
Preferred
1 | func namePickerView(_ namePickerView: NamePickerView, didSelectName name: String) |
Not Preferred:
1 | func didSelectName(namePicker: NamePickerViewController, name: String) |
使用类型推断上下文
使用类型推断可以使代码整洁 (可参考Type Inference.)
Preferred:
1 | let selector = #selector(viewDidLoad) |
Not Preferred:
1 | let selector = #selector(ViewController.viewDidLoad) |
泛型
泛型命名应该是可描述性的,使用驼峰命名。当类型名字没有意义时,使用传统的大写字母:T,U 或者 V
Preferred:
1 | struct Stack<Element> { ... } |
Not Preferred:
1 | struct Stack<T> { ... } |
类和结构体
应该用哪个
结构体是值类型。使用结构体的对象是不具有唯一性,例如:数组[a,b,c]和另外的一个定义在其他的数组[a,b,c]是可以互换的。不管是第一个数组还是第二个数组,它们所代表的含义是一样的。这就是为什么数组是结构体的原因。
类是引用类型。对拥有自己的id或者有生命周期的事物使用类。常常把人建模为一个类,两个人的对象是不同的东西,就算他们有相同的名字和生日,也不意味着他们是同一个人。但是生日应该为结构体,因为1950年3月的日期跟其他相同日期表示的意思是一样的。
例子
这是一个比较不错的定义Class的例子
1 | class Circle: Shape { |
使用Self
为了简洁起见,避免使用self
,因为Swift
不强求使用self
来访问对象的属性或者调用方法。
只有在编译器要求使用self
的时候,(在@escaping
闭包里面或者在对象初始化的方法里面为了消除歧义),否则在没有编译器提醒时都应该省略它
计算属性
Preferred:
1 | var diameter: Double { |
Not Preferred:
1 | var diameter: Double { |
Final关键字
如果你的类不需要派生子类,那就给类定义为final
吧
1 | // Turn any generic type into a reference type using this Box class. |
函数定义
函数定义在一行可以定义完包括大括号
1 | func reticulateSplines(spline: [Double]) -> Bool { |
多个参数,让每个参数应该在新的一行
1 | func reticulateSplines( |
使用Void表示缺省
Preferred:
1 | func updateConstraints() -> Void { |
Not Preferred:
1 | func updateConstraints() -> () { |
函数调用
Mirror the style of function declarations at call sites. Calls that fit on a single line should be written as such:
1 | let success = reticulateSplines(splines) |
If the call site must be wrapped, put each parameter on a new line, indented one additional level:
1 | let success = reticulateSplines( |
闭包表达式
只有参数列表末尾有一个闭包表达式参数时,才使用尾随闭包。否则还是应该加上参数名字
Preferred:
1 | UIView.animate(withDuration: 1.0) { |
Not Preferred:
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) |
类型
尽量使用Swfit
的原生类型表达式
Preferred:
1 | let width = 120.0 // Double |
Less Preferred:
1 | let width = 120.0 // Double |
Not Preferred:
1 | let width: NSNumber = 120.0 // NSNumber |
在使用CG
开头的相关类型,使用CGFloat
会使代码更加清晰易读
常量
常量可以应该用let
关键字定义
Tip: 最好是全部都使用let
,只有要编译有问题时才使用var
定义类常量比实例常量会更好。定义类常量使用static let
关键字。
Preferred:
1 | enum Math { |
Not Preferred:
1 | let e = 2.718281828459045235360287 // pollutes global namespace |
Optionals 可选类型
如果定义变量和函数返回值有可能为nil
,应该定义为可选值?
使用!
定义强制解包类型,只有在你接下来会明确该变量被会初始化。例如:将会在viewDidLoad()
方法实例的子view。
访问可选类型时,如果有多个类型或者只访问一次,可以使用语法链
1 | textContainer?.textLabel?.setNeedsDisplay() |
多处使用应该使用一次性绑定
1 | if let textContainer = textContainer { |
是否可以类型不应该出现在命名中,例如:optionalString
、maybeView
、unwrappedView
,因为这些信息已经包含在类型声明中
Preferred:
1 | var subview: UIView? |
Not Preferred:
1 | var optionalSubview: UIView? |
类型推断
让编译器推断变量或者常量的类型,当真需要时,才会指定特定的类型,例如: CGFloat
和 Int16
Preferred:
1 | let message = "Click the button" |
Not Preferred:
1 | let message: String = "Click the button" |
语法糖
尽量使用较短快捷的定义版本
Preferred:
1 | var deviceModels: [String] |
Not Preferred:
1 | var deviceModels: Array<String> |
内存管理
代码无论在什么时候都不应该产生循环引用,使用weak
和unowned
引用防止产生循环引用,或者使用值类型。
延长对象寿命
延长对象寿命使用[weak self]
和 guard let = self else { return }
语法。
Preferred
1 | resource.request().onComplete { [weak self] response in |
Not Preferred
1 | // might crash if self is released before response returns |
Not Preferred
1 | // deallocate could happen between updating the model and updating UI |
控制流
Prefer the for-in
style of for
loop over the while-condition-increment
style.
Preferred:
1 | for _ in 0..<3 { |
Not Preferred:
1 | var i = 0 |
三元表达式
Preferred:
1 | let value = 5 |
Not Preferred:
1 | result = a > b ? x = c > d ? c : d : y |
黄金路径
当使用条件编写代码时,应该及时return
。也就是说,不要嵌套if
语句,关键字guard
你值得了解
Preferred:
1 | func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies { |
Not Preferred:
1 | func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies { |
如果是多个条件的情况下,使用guard
更加清晰明了。例子:
Preferred:
1 | guard |
Not Preferred:
1 | if let number1 = number1 { |
分号
Swift 不要求在每句语句后面加分号,只要在多句语句在同一行的时才需要分号
不要把多行语句在同一行用分号隔开来
Preferred:
1 | let swift = "not a scripting language" |
Not Preferred:1
let swift = "not a scripting language";
括号
在条件语句中括号不是一定要的,应该省略掉。
Preferred:
1 | if name == "Hello" { |
Not Preferred:
1 | if (name == "Hello") { |
很长的表达式中,使用括号可以使代码更加清晰
Preferred:
1 | let playerMark = (player == current ? "X" : "O") |
多行字符串
当创建很长字符串时,应该使用多行字符串语法
Preferred:
1 | let message = """ |
Not Preferred:
1 | let message = """You cannot charge the flux \ |
Not Preferred:
1 | let message = "You cannot charge the flux " + |