Swift style guide

分類:技術 時間:2016-10-25

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

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) -gt; CGPoint
func timedAction(afterDelay delay: NSTimeInterval, perform action: SKAction) -gt; 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。如果不能起一個有意義的關系或角色名稱,可以使用 TUV

Preferred:

struct Stacklt;Elementgt; { ... }
func writeTolt;Target: OutputStreamgt;(inout target: Target)
func maxlt;T: Comparablegt;(x: T, _ y: T) -gt; T

Not Preferred:

struct Stacklt;Tgt; { ... }
func writeTolt;target: OutputStreamgt;(inout t: target)
func maxlt;Thing: Comparablegt;(x: Thing, _ y: Thing) -gt; Thing

Language

使用美國英語。

Preferred:

let color = quot;redquot;

Not Preferred:

let colour = quot;redquot;

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) -gt; Int {
   // #warning Incomplete implementation, return the number of sections
   return 1
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -gt; Int {
  // #warning Incomplete implementation, return the number of rows
  return Database.contacts.count
}

Preferred:

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -gt; 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] = [quot;Aquot;: 1.2, quot;Bquot;: 3.2]
}

Not Preferred:

class TestDatabase : Database {
  var data :[String:CGFloat] = [quot;Aquot; : 1.2, quot;Bquot;:3.2]
}

Comments

盡可能避免大量使用注釋,好的代碼應該盡可能是self-documenting。

如果需要注釋,它只用來解釋 為什么 這段代碼要這么寫,而不是解釋代碼的邏輯。

并且是正確的,代碼變化時也需要馬上更新,不能有誤導。

Exception: 上面兩條不適用于生成文檔用的注釋.

Classes and Structures

Which one to use?

Structs是 value semantics

而Classes是 reference semantics .

有些時候,只需要Structs就夠了。但有些Class,由于歷史原因,被弄成類,如 NSDateNSSet 等。

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() -gt; String {
    return quot;I am a circle at \(centerString()) with an area of \(computeArea())quot;
  }

  override func computeArea() -gt; Double {
    return M_PI * radius * radius
  }

  private func centerString() -gt; String {
    return quot;(\(x),\(y))quot;
  }
}

上面的例子,給我們演示了這些規范:

  • 冒號在用于指明類型時,左邊沒有空格,右邊有一個空格。
  • 當一組變量、常量有關聯時,定義在一行里。
  • 函數修飾符 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

For conciseness, if a computed property is read-only, omit the get clause. The get clause is required only when a set clause is provided.

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 Boxlt;Tgt; {
  let value: T 
  init(_ value: T) {
    self.value = http://www.tuicool.com/articles/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()
}

For single-expression closures where the context is clear, use implicit returns:

只有一個表達式的、用來返回值的閉包,可以省略return。

attendeeList.sort { a, b in
  a gt; 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。

You can define constants on a type rather than an instance of that type using type properties. To declare a type property as a constant simply use static let . Type properties declared in this way are generally preferred over global constants because they are easier to distinguish from instance properties. Example:

Preferred:

enum Math {
  static let e  = 2.718281828459045235360287
  static let pi = 3.141592653589793238462643
}

radius * Math.pi * 2 // circumference

Note:The advantage of using a case-less enumeration is that it can't accidentally be instantiated and works as a pure namespace.

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變量命名時,不要使用類似 optionalStringmaybeView 這樣的方式,因為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.infiniteCGRect.null , etc, 而不是global constants CGRectInfinite , CGRectNull , etc。

Type Inference

對于適用于類型推斷的地方,使用類型推斷。

Preferred:

let message = quot;Click the buttonquot;
let currentBounds = computeViewBounds()
var names = [quot;Micquot;, quot;Samquot;, quot;Christinequot;]
let maximumWidth: CGFloat = 106.5

Not Preferred:

let message: String = quot;Click the buttonquot;
let currentBounds: CGRect = computeViewBounds()
let names = [String]()

Syntactic Sugar

盡可能使用語法糖。

Preferred:

var deviceModels: [String]
var employees: [Int: String]
var faxNumber: Int?

Not Preferred:

var deviceModels: Arraylt;Stringgt;
var employees: Dictionarylt;Int, Stringgt;
var faxNumber: Optionallt;Intgt;

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 = http://www.tuicool.com/articles/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 -gt; Frequencies {

  guard let context = context else { throw FFTError.noContext }
  guard let inputData = http://www.tuicool.com/articles/inputData else { throw FFTError.noInputData }

  // use context and input to compute the frequencies

  return frequencies
}

Not Preferred:

func computeFFT(context: Context?, inputData: InputData?) throws -gt; Frequencies {

  if let context = context {
    if let inputData = http://www.tuicool.com/articles/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(quot;impossiblequot;) }
// do something with numbers

Not Preferred:

if let number1 = number1 {
  if let number2 = number2 {
    if let number3 = number3 {
      // do something with numbers
    }
    else {
      fatalError(quot;impossiblequot;)
    }
  }
  else {
    fatalError(quot;impossiblequot;)
  }
}
else {
  fatalError(quot;impossiblequot;)
}

Failing Guards

Guard檢查失敗執行的語句應該退出當前方法,并且應該只有一條語句,如 return , throw , break , continue , and fatalError() 。如果需要多行語句,考慮使用 defer

Parentheses

保住條件語句的圓括號應該省略。

Preferred:

if name == quot;Helloquot; {
  print(quot;Worldquot;)
}

Not Preferred:

if (name == quot;Helloquot;) {
  print(quot;Worldquot;)
}

Other Swift Style Guides

上面的規范可能你不太喜歡,或者沒有涉及到你需要的某些方面,可以參考下面的內容:


Tags: Swift

文章來源:http://www.jianshu.com/p/32e62ac53377


ads
ads

相關文章
ads

相關文章

ad