Introduction
之前有一个Objective-C style guide.,这一篇是针对swift的一个补充。
Credits
API Design Guidelines是苹果专门针对API的一个规范,本规范涉及到一些相关的内容,大都是保持和苹果的规范一致。
它绝大部分内容,来自The Official raywenderlich.com Swift Style Guide.,并加入了自己的一些偏好。
同时,Swift语言在快速的发展中,这个规范也会随着Swift的发展、以及我们对Swift更多的使用和了解,持续地进行修改和完善。
Table of Contents
- Naming
- Protocols
- Enumerations
- Generics
- Class Prefixes
- Language
- Code Organization
- Protocol Conformance
- Unused Code
- Minimal Imports
- Spacing
- Comments
- Classes and Structures
- Use of Self
- Protocol Conformance
- Computed Properties
- Final
- Closure Expressions
- Types
- Constants
- Optionals
- Struct Initializers
- Lazy Initialization
- Type Inference
- Syntactic Sugar
- Functions vs Methods
- Memory Management
- Access Control
- Golden Path
- Failing Guards
- Parentheses
- Other Swift Style Guides
Naming
classes, structures, enumerations 和 protocols采用首字母大写的驼峰命名法,method names and variables采用首字母小写的驼峰命名法。
Preferred:
private let maximumWidgetCount = 100
class WidgetContainer {
var widgetButton: UIButton
let widgetHeightPercentage = 0.85
}
Not Preferred:
let MAX_WIDGET_COUNT = 100
class app_widgetContainer {
var wBut: UIButton
let wHeightPct = 0.85
}
缩写应该避免,但如URL、ID这种常见的缩写可以使用。
API Design Guidelines中提到,如果使用这些缩写,字母应该全为大写或者小写。Examples:
Preferred
let urlString: URLString
let userID: UserID
Not Preferred
let uRLString: UrlString
let userId: UserId
使用argument label,替代注释,使代码self-documenting,可读性更强。
func convertPointAt(column: Int, row: Int) -> CGPoint
func timedAction(afterDelay delay: NSTimeInterval, perform action: SKAction) -> SKAction!
// would be called like this:
convertPointAt(column: 42, row: 13)
timedAction(afterDelay: 1.0, perform: someOtherAction)
Protocols
按照苹果的API Design Guidelines,Protocols名字可以使用名词来描述这个Protocols的内容,比如Collection
, WidgetFactory
。
或以-ing、-able结尾来描述Protocols实现的一些功能,比如: Equatable
, Resizing
。
Enumerations
按照苹果的API Design Guidelines,枚举值使用小写开头的驼峰命名法,也就是lowerCamelCase。
enum Shape {
case rectangle
case square
case rightTriangle
case equilateralTriangle
}
Class Prefixes
在Swift里面,每一个module都是一个namesapce。而在ObjC里没有namespace的概念,只是在每个类名前面添加前缀,比如NSArray。
当不同的module有同名的类名时,需要指明module name。
import SomeModule
let myClass = MyModule.UsefulClass()
Generics
范型的类型名,应该是描述性的名词、upperCamelCase。如果不能起一个有意义的关系或角色名称,可以使用T
、U
或V
。
Preferred:
struct Stack { ... }
func writeTo(inout target: Target)
func max(x: T, _ y: T) -> T
Not Preferred:
struct Stack { ... }
func writeTo(inout t: target)
func max(x: Thing, _ y: Thing) -> Thing
Language
使用美国英语。
Preferred:
let color = "red"
Not Preferred:
let colour = "red"
Code Organization
多使用extensions来组织代码。每个 extensions使用// MARK: -
来分隔开。
Protocol Conformance
当一个类遵守一个协议时,使用extensions来组织代码。
Preferred:
class MyViewcontroller: UIViewController {
// class stuff here
}
// MARK: - UITableViewDataSource
extension MyViewcontroller: UITableViewDataSource {
// table view data source methods
}
// MARK: - UIScrollViewDelegate
extension MyViewcontroller: UIScrollViewDelegate {
// scroll view delegate methods
}
Not Preferred:
class MyViewcontroller: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
// all methods
}
Unused Code
能删除的代码,都删掉。
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
}
Preferred:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Database.contacts.count
}
Minimal Imports
尽可能减少依赖和imports。
Spacing
- 缩进使用2个空格:
- 方法体的花括号需要在新的一行开启,在新的一行关闭。而其它花括号(
if
/else
/switch
/while
etc.),加入一个空格后在行尾开启,在新一行关闭(Xcode默认)。 - 提示:⌘A选中代码后使用Control-I (或者菜单Editor\Structure\Re-Indent)来调整缩进.
Preferred:
if user.isHappy {
// Do something
} else {
// Do something else
}
Not Preferred:
if user.isHappy
{
// Do something
}
else {
// Do something else
}
- methods之间只留一个空行。methods内部,使用空行来分隔不同功能的代码,为不同的section。一个method内部的section不宜太多,否则应该考虑分拆。
- 冒号左边没有空格,右边有一个空格。Exception:
? :
和[:]
。
Preferred:
class TestDatabase: Database {
var data: [String: CGFloat] = ["A": 1.2, "B": 3.2]
}
Not Preferred:
class TestDatabase : Database {
var data :[String:CGFloat] = ["A" : 1.2, "B":3.2]
}
Comments
尽可能避免大量使用注释,好的代码应该尽可能是self-documenting。
如果需要注释,它只用来解释为什么这段代码要这么写,而不是解释代码的逻辑。
并且是正确的,代码变化时也需要马上更新,不能有误导。
Exception: 上面两条不适用于生成文档用的注释.
Classes and Structures
Which one to use?
Structs是value semantics。
而Classes是reference semantics.
有些时候,只需要Structs就够了。但有些Class,由于历史原因,被弄成类,如NSDate
、NSSet
等。
Example definition
下面是一个比较规范的Class定义:
class Circle: Shape {
var x: Int, y: Int
var radius: Double
var diameter: Double {
get {
return radius * 2
}
set {
radius = newValue / 2
}
}
init(x: Int, y: Int, radius: Double) {
self.x = x
self.y = y
self.radius = radius
}
convenience init(x: Int, y: Int, diameter: Double) {
self.init(x: x, y: y, radius: diameter / 2)
}
func describe() -> String {
return "I am a circle at \(centerString()) with an area of \(computeArea())"
}
override func computeArea() -> Double {
return M_PI * radius * radius
}
private func centerString() -> String {
return "(\(x),\(y))"
}
}
上面的例子,给我们演示了这些规范:
- 冒号在用于指明类型时,左边没有空格,右边有一个空格。
- 当一组变量、常量有关联时,定义在一行里。
- 函数修饰符
internal
是缺省值,可以省略。重载一个函数时,访问修饰符也可以省略掉。
Use of Self
避免使用self来访问属性。除非需要区分函数参数和属性。
class BoardLocation {
let row: Int, column: Int
init(row: Int, column: Int) {
self.row = row
self.column = column
let closure = {
print(self.row)
}
}
}
Computed Properties
Computed property一般是只读,同时省略get clause。get clause只是当set clause存在时才需要写。
Preferred:
var diameter: Double {
return radius * 2
}
Not Preferred:
var diameter: Double {
get {
return radius * 2
}
}
Final
当一个类不想被继承时,使用final
关键字。Example:
// Turn any generic type into a reference type using this Box class.
final class Box {
let value: T
init(_ value: T) {
self.value = value
}
}
Closure Expressions
方法的参数列表最后一参数类型为闭包时,可以使用尾闭包。但只在只存在一个闭包参数时才使用尾闭包。
Preferred:
UIView.animateWithDuration(1.0) {
self.myView.alpha = 0
}
UIView.animateWithDuration(1.0,
animations: {
self.myView.alpha = 0
},
completion: { finished in
self.myView.removeFromSuperview()
}
)
Not Preferred:
UIView.animateWithDuration(1.0, animations: {
self.myView.alpha = 0
})
UIView.animateWithDuration(1.0,
animations: {
self.myView.alpha = 0
}) { f in
self.myView.removeFromSuperview()
}
只有一个表达式的、用来返回值的闭包,可以省略return。
attendeeList.sort { a, b in
a > b
}
Types
尽可能使用Swift原生类型,而不是使用ObjC的NS类型。
Preferred:
let width = 120.0 // Double
let widthString = (width as NSNumber).stringValue // String
Not Preferred:
let width: NSNumber = 120.0 // NSNumber
let widthString: NSString = width.stringValue // NSString
但是有些情况例外,比如在写Sprite Kit代码时,用CGFloat
可以减少转换。
Constants
尽可能使用let,只有在需要使用变量的时候使用var。
Tip: 有一个办法能达到上面的目的,就是自己只写let,让编译器帮你确定哪些需要改成var。
使用static let
定义类常量,而不是实例常量,或者全局常量。
Preferred:
enum Math {
static let e = 2.718281828459045235360287
static let pi = 3.141592653589793238462643
}
radius * Math.pi * 2 // circumference
Note: 使用枚举定义常量的好处是让常量定义在特定的命名空间。
Not Preferred:
let e = 2.718281828459045235360287 // pollutes global namespace
let pi = 3.141592653589793238462643
radius * pi * 2 // is pi instance data or a global constant?
Optionals
当访问一个optional value前,需要访问多个optional value时,使用optional chaining:
self.textContainer?.textLabel?.setNeedsDisplay()
访问一个optional value后,需要执行多个操作,可以使用optional binding:
if let textContainer = self.textContainer {
// do many things with textContainer
}
给optional变量命名时,不要使用类似optionalString
或maybeView
这样的方式,因为optional-ness已经在类型声明中体现。
相对应的,使用unwrapped value时,避免使用unwrappedView或
actualLabel`,使用optional变量名就可以了。
Preferred:
var subview: UIView?
var volume: Double?
// later on...
if let subview = subview, volume = volume {
// do something with unwrapped subview and volume
}
Not Preferred:
var optionalSubview: UIView?
var volume: Double?
if let unwrappedSubview = optionalSubview {
if let realVolume = volume {
// do something with unwrappedSubview and realVolume
}
}
Struct Initializers
使用Swfit原生的struct initializers,而不是遗留的CGGeometry constructors。
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)
同样的,使用struct-scope constants CGRect.infinite
, CGRect.null
, etc, 而不是global constants CGRectInfinite
, CGRectNull
, etc。
Type Inference
对于适用于类型推断的地方,使用类型推断。
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]()
Syntactic Sugar
尽可能使用语法糖。
Preferred:
var deviceModels: [String]
var employees: [Int: String]
var faxNumber: Int?
Not Preferred:
var deviceModels: Array
var employees: Dictionary
var faxNumber: Optional
Functions vs Methods
不依附于任何class或type的函数被称为free function,应该尽量避免使用,因为不太好找到这个方法。
Preferred
let sorted = items.mergeSort() // easily discoverable
rocket.launch() // clearly acts on the model
Not Preferred
let sorted = mergeSort(items) // hard to discover
launch(&rocket)
Free Function Exceptions
let tuples = zip(a, b) // feels natural as a free function (symmetry)
let value = max(x,y,z) // another free function that feels natural
Memory Management
用下面prefered的方式,避免循环引用。
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)
}
Access Control
访问修饰符应该放在靠前的位置,前面只能有static
、@IBAction
和 @IBOutlet
。
Preferred:
class TimeMachine {
private dynamic lazy var fluxCapacitor = FluxCapacitor()
}
Not Preferred:
class TimeMachine {
lazy dynamic private var fluxCapacitor = FluxCapacitor()
}
Golden Path
嵌套的if
会让代码的缩进层次不齐(整齐的缩进被称作Golden Path),会让代码可读性变差,使用guard
来做函数输入合法性检查,可以减少if嵌套。
Preferred:
func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
guard let context = context else { throw FFTError.noContext }
guard let inputData = inputData else { throw FFTError.noInputData }
// use context and input to compute the frequencies
return frequencies
}
Not Preferred:
func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
if let context = context {
if let inputData = inputData {
// use context and input to compute the frequencies
return frequencies
}
else {
throw FFTError.noInputData
}
}
else {
throw FFTError.noContext
}
}
Preferred:
guard let number1 = number1, number2 = number2, number3 = number3 else { fatalError("impossible") }
// do something with numbers
Not Preferred:
if let number1 = number1 {
if let number2 = number2 {
if let number3 = number3 {
// do something with numbers
}
else {
fatalError("impossible")
}
}
else {
fatalError("impossible")
}
}
else {
fatalError("impossible")
}
Failing Guards
Guard检查失败执行的语句应该退出当前方法,并且应该只有一条语句,如return
, throw
, break
, continue
, and fatalError()
。如果需要多行语句,考虑使用defer
。
Parentheses
保住条件语句的圆括号应该省略。
Preferred:
if name == "Hello" {
print("World")
}
Not Preferred:
if (name == "Hello") {
print("World")
}
Other Swift Style Guides
上面的规范可能你不太喜欢,或者没有涉及到你需要的某些方面,可以参考下面的内容:
- GitHub