Swift設計指南

這是一個Swift開發代碼規範git

正確性

儘量地讓你的代碼在沒有警告的狀況下編譯。這個規則適用不少的狀況,好比#selector類型去替代適用字符串字面值。github

命名

命名遵循如下關鍵點:swift

  • 儘量的明確使用場景
  • 清晰比簡短更重要
  • 使用駝峯命名法
  • 類型和協議使用大寫字母開頭,其餘的小寫字母開頭
  • 包含全部你須要的關鍵字,省略沒必要要的關鍵字
  • 用使用角色來命名,而不是類型
  • 弱引用類型要作註釋說明
  • 要善用控制流
  • 工廠方法用make開頭
  • 方法命名遵循如下規則
    • 動做化的方法遵循-ed,-ing這樣的規則
    • 名詞化的方法遵循相似formX這樣的規則就能夠了
    • boolean類型的方法,命名出來是要給人看到是用來作判斷用的
    • protocols的描述應該名詞化
    • protocols若是是描述一個「可否......」的時候,應該以-able或者ible結束
  • 使用專業的術語
  • 避免縮寫
  • 儘量經過方法 和屬性來實現功能。
  • 命名要具備約束力
  • 上下文的首字母縮寫要保持一致性
  • 避免函數返回類型重載
  • 選擇好的參數命名看成文檔使用
  • 閉包和元組的參數要寫標識
  • 要善用默認參數

Xcode的方法屬性簡介列表

(img) 當你在這個列表裏參閱的時候,清晰的含義就顯得格外重要了。你須要讓別人儘量簡單的去參閱一個方法名。數組

  1. 編寫沒有參數的方法。好比:addTarget
  2. 編寫方法的時候要寫參數的標籤。

Class的前綴

Swift裏面的類型已經自動地增長了它們所屬模塊的命名空間,你不須要爲它們編寫前綴。若是來自不一樣模塊的兩個名稱發生衝突,您能夠經過使用模塊名稱預先肯定類型名稱來消除歧義。可是,只有在可能出現混淆的狀況下才指定模塊名稱。(可是在tdw項目裏面,仍是建議使用前綴,這個前綴是用來做爲tdw出品的標識)xcode

import SomeModule                               
let myClass = MyModule.UsefulClass()
複製代碼

代理

當你自定義一個代理方法等時候,第一個參數應該是一個匿名參數,這個參數是代理源。(UIkit包含了不少這樣的例子) 正確的:安全

func namePickerView(_ namePickerView: NamePickerView, didSelectName name: String)
func namePickerViewShouldReload(_ namePickerView: NamePickerView) -> Bool
複製代碼

錯誤的:bash

func namePickerView(_ namePickerView: NamePickerView, didSelectName name: String)
func namePickerViewShouldReload(_ namePickerView: NamePickerView) -> Bool
複製代碼

使用上下文類型推斷

利用編譯器上下文類型推斷編寫更短更簡潔的代碼。閉包

正確的:app

let selector = #selector(viewDidLoad)
view.backgroundColor = .red
let toView = context.view(forKey: .to)
let view = UIView(frame: .zero)
複製代碼

錯誤的ide

let selector = #selector(ViewController.viewDidLoad)
view.backgroundColor = UIColor.red
let toView = context.view(forKey: UITransitionContextViewKey.to)
let view = UIView(frame: CGRect.zero)
複製代碼

模版

範型類型應該被有意義的描述,用駝峯命名首字母大寫的方式編寫。若是你找不到詞語用來表達這個類型的意思,可使用傳統的26個字母中的某一個,大寫,來表示,好比:T,U,V等。 正確的:

struct Stack<Element> { ... }
func write<Target: OutputStream>(to target: inout Target)
func swap<T>(_ a: inout T, _ b: inout T)
複製代碼

錯誤的:

struct Stack<T> { ... }
func write<target: OutputStream>(to target: inout target)
func swap<Thing>(_ a: inout Thing, _ b: inout Thing)
複製代碼

語言

使用美式英語來描述API 正確的:

let color = "red"
複製代碼

錯誤的:

let colour = "red"
複製代碼

代碼組織

使用extensions來組織你的代碼邏輯塊。每一個extension都應該使用// MARK: -來講明該extension的功能。

協議的遵循

特別的,當你增長一個繼承協議的時候,最好用extension分割協議方法。

正確的:

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
}
複製代碼

錯誤的:

class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
  // all methods
}
複製代碼

無用的代碼

沒有用的代碼,該刪掉的仍是要刪掉,不要只是註釋掉

間隔

xcode設置:

控制流

正確的:

if user.isHappy {
  // Do something
} else {
  // Do something else
}
複製代碼

錯誤的:

if user.isHappy
{
  // Do something
}
else {
  // Do something else
}
複製代碼

關於空格的使用:

正確的:

class TestDatabase: Database {
  var data: [String: CGFloat] = ["A": 1.2, "B": 3.2]
}
複製代碼

錯誤的:

class TestDatabase: Database {
  var data: [String: CGFloat] = ["A": 1.2, "B": 3.2]
}
複製代碼

註釋

當須要時,使用註釋來解釋爲何特定的代碼會作一些事情。註釋必須保持最新或刪除。

類和結構體

該使用哪一個?

記住,struct具備值語義。對於沒有身份的事物使用結構。包含a、b、c的數組與包含a、b、c的另外一個數組徹底相同,它們是徹底可互換的。無論你用的是第一個數組仍是第二個數組,由於它們表示的是徹底同樣的東西。這就是爲何數組是結構的緣由。

類具備引用語義。爲具備標識或特定生命週期的事物使用類。你能夠將一我的建模爲一個類,由於兩個對象是兩個不一樣的東西。僅僅由於兩我的有相同的名字和生日,並不意味着他們是同一我的。可是這我的的生日是一個結構體,由於1950年3月3日的日期和1950年3月3日的任何其餘日期對象是同樣的。日期自己沒有標識。

定義的例子

下面是一個精心設計的類定義示例:

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)
  }

  override func area() -> Double {
    return Double.pi * radius * radius
  }
}

extension Circle: CustomStringConvertible {
  var description: String {
    return "center = \(centerString) area = \(area())"
  }
  private var centerString: String {
    return "(\(x),\(y))"
  }
}
複製代碼

上面的例子遵循如下的設計指南:

  • 屬性、變量、常量、參數聲明和其餘在冒號以後(不在以前)有空格的語句的指定類型,例如x: Int和Circle: Shape。
  • 若是共享一個共同目的/上下文,則在一行中定義多個變量和結構。
  • 縮進getter和setter定義和屬性觀察者。
  • 不要添加修飾符,例如internal當它們已是默認值時。一樣,在覆蓋方法時,不要重複使用修飾符。
  • 在擴展中添加額外的功能 (e.g. printing) 。
  • 隱藏非共享、實現細節如 centerString 擴展內部使用 private 訪問控制的代碼。

Self的使用

爲了簡潔起見,當不須要訪問對象的屬性或調用它的方法時要避免使用‘self’。

只有當編譯器要求必須使用self的時候才使用。

計算屬性:

爲了簡潔起見,若是一個計算屬性是隻讀的,則省略get子句。只有在提供了set子句時,才須要get子句。

正確的:

var diameter: Double {
    return radius * 2
}
複製代碼

錯誤的:

var diameter: Double {
    get {
        return radius * 2
    }
}
複製代碼

Final

不容許對其修飾的內容進行繼承或者從新操做。
final標記class或members可能會分散主題,這沒必要需。然而,使用 final 有時能夠闡明你的意圖,是值得的。在如下示例中, Box 有一個特殊用途而且並不打算容許在派生類定製。標記 final 會讓這很清楚。

// Turn any generic type into a reference type using this Box class.
final class Box<T> {
    let value: T
    init(_ value: T) {
        self.value = value
    }
}
複製代碼

函數聲明

在一行上使用簡潔的功能聲明(包括大括號)

func reticulateSplines(spline: [Double]) -> Bool {
   // reticulate code goes here
}
對於具備長簽名的功能, 在適當的點添加換行符,並後續行上添加一個額外的縮進
func reticulateSplines(spline: [Double], adjustmentFactor: Double,
        translateConstant: Int, comment: String) -> Bool {
     // reticulate code goes here
}
複製代碼

閉包表達式

僅在參數列表末尾有單個閉包表達式參數時才使用尾隨閉包。並給出閉包參數的描述性名稱。

正確的:

UIView.animate(withDuration: 1.0) {
  self.myView.alpha = 0
}

UIView.animate(withDuration: 1.0, animations: {
  self.myView.alpha = 0
}, completion: { finished in
  self.myView.removeFromSuperview()
})
複製代碼

錯誤的:

UIView.animate(withDuration: 1.0, animations: {
  self.myView.alpha = 0
})

UIView.animate(withDuration: 1.0, animations: {
  self.myView.alpha = 0
}) { f in
  self.myView.removeFromSuperview()
}
複製代碼

對於上下文清晰的簡單閉包表達式,能夠用隱式返回:

attendeeList.sort { a, b in
  a > b
}
複製代碼

使用尾隨閉包的連接方法在上下文中應該清晰易讀。關於間距,換行,何時使用命名和匿名的參數,這些都留給做者自行判斷。

let value = numbers.map { $0 * 2 }.filter { $0 % 3 == 0 }.index(of: 90)

let value = numbers
  .map {$0 * 2}
  .filter {$0 > 50}
  .map {$0 + 10}
複製代碼

類型

儘量使用Swift自帶的類型。Swift爲Objective-C提供了橋接文件,當你須要時你仍然可使用它全部的方法。

正確的:

let width = 120.0                                    // Double
let widthString = (width as NSNumber).stringValue    // String
複製代碼

錯誤的:

let width: NSNumber = 120.0                          // NSNumber
let widthString: NSString = width.stringValue        // NSString
複製代碼

常量

定義常量使用 let 關鍵字, 變量使用 var 關鍵字。若是變量的值不會改變一般使用 let,而不是 var 。

提示:有一個好方法就是全部東西都使用 let 定義,編譯器報警告時再把它改成 var

您能夠在類型上定義常量,而不是使用類型屬性定義該類型的實例 。使用 static let 聲明一個僅僅做爲常數的類型屬性。用這種方式聲明的類型屬性一般優於全局常量,由於他們更容易從實例屬性中區分。
正確的:

enum Math {
  static let e = 2.718281828459045235360287
  static let root2 = 1.41421356237309504880168872
}

let hypotenuse = side * Math.root2
註解:使用這種枚舉的優勢是它不會被不當心實例化或者只是一個單純的命名空間
複製代碼

錯誤的:

let e = 2.718281828459045235360287  // pollutes global namespace
let root2 = 1.41421356237309504880168872

let hypotenuse = side * root2 // what is root2?
複製代碼

靜態方法和變量類型屬性

靜態方法和類型屬性的工做方式相似於全局函數和全局變量,應該謹慎使用。當功能侷限於某一特定類型或者要求與Objective-C互操做時是頗有用的。

可選類型

將變量和函數返回類型聲明爲可選類型,返回一個nil值是可接受的。

只有在實例變量使用前肯定會初始化的前提下,才能使用 !隱式強解的類型聲明,例如將設置的子視圖viewDidLoad。

訪問可選值時,若是該值僅訪問一次,或者鏈中有多個可選項,請使用可選連接:

self.textContainer?.textLabel?.setNeedsDisplay()
複製代碼

使用可選綁定更方便一次打開並執行多個操做:

if let textContainer = self.textContainer {
  // do many things with textContainer
}
複製代碼

當命名可選變量和屬性時,避免像optionalString或maybeView這樣命名它們,由於它們的可選參數已經在類型聲明中。

對於可選綁定適當隱藏原名,而不是使用unwrappedView或actualLabel這樣的名稱。

正確的:

var subview: UIView?
var volume: Double?

// later on...
if let subview = subview, let volume = volume {
  // do something with unwrapped subview and volume
}
複製代碼

錯誤的:

var optionalSubview: UIView?
var volume: Double?

if let unwrappedSubview = optionalSubview {
  if let realVolume = volume {
    // do something with unwrappedSubview and realVolume
  }
}
複製代碼

懶加載

考慮使用懶加載細粒度控制對象生命週期。對於UIViewController那些加載視圖而言,這一點尤爲如此。您可使用當即調用的閉包 ()或調用私有工廠方法。

lazy var locationManager: CLLocationManager = self.makeLocationManager()

private func makeLocationManager() -> CLLocationManager {
  let manager = CLLocationManager()
  manager.desiredAccuracy = kCLLocationAccuracyBest
  manager.delegate = self
  manager.requestAlwaysAuthorization()
  return manager
}
複製代碼

註釋: [unowned self]這裏不須要 沒有建立保留循環。 位置管理器具備彈出UI的反作用,要求用戶得到許可,所以細粒度控制在這裏是有意義的。

類型推斷

但願使用簡潔的代碼,讓編譯器推斷單個實例的常量或變量的類型。類型推斷也適用於小(非空)數組和字典。須要時,指定特定的類型如CGFloat或Int16。

正確的:

let message =  「點擊按鈕」
let currentBounds =  computeViewBounds()
var names = [ 「 Mic 」,「 Sam 」,「 Christine 」 ]
let maximumWidth : CGFloat =  106.5
複製代碼

錯誤的:

let message : String  =  「點擊按鈕」
let currentBounds : CGRect =  computeViewBounds()
let names = [ String ]()
複製代碼

空數組和字典的類型註釋: 對於空數組和字典,使用類型註釋。(對於分配了大量多行內容的數組或字典,使用類型標註)

正確的:

var names: [String] = []
var lookup: [String: Int] = [:]
複製代碼

錯誤的:

var names = [String]()
var lookup = [String: Int]()
複製代碼

注意:遵循此準則意味着選擇描述性名稱比之前更重要。

語法糖

與完整的泛型語法相比,更傾向於使用簡潔版的類型聲明。

正確的:

var deviceModels: [String]
var employees: [Int: String]
var faxNumber: Int?
複製代碼

錯誤的:

var deviceModels: Array<String>
var employees: Dictionary<Int, String>
var faxNumber: Optional<Int>
複製代碼

功能與方法

對於不附加到類或類型的不受約束的功能,應該謹慎使用。若是能夠,儘量使用‘.’方法而不是不受約束的功能。這有助於可讀性和可發現性。 正確的:

let sorted = items.mergeSorted()  // easily discoverable
rocket.launch()  // acts on the model
複製代碼

錯誤的:

let sorted = mergeSort(items)  // hard to discover
launch(&rocket)
複製代碼

自由功能異常:

let tuples = zip(a, b)  // feels natural as a free function (symmetry)
let value = max(x, y, z)  // another free function that feels natural
複製代碼

內存管理

代碼不該該建立引用循環,即便是非生產或教程demo中。分析您的對象圖,並使用weak或unowned防止強烈的循環。或者,使用值類型(struct,enum)來徹底防止循環。

延長對象生命週期

使用【weak self】和 guard let strongSelf = self else { return }語句擴展對象生命週期。【weak self】相對於【unowned self】的優點不是特別明顯時,不使用self。明顯延長使用週期,顯然比可選展開更好。

推薦的:

resource.request().onComplete { [weak self] response in
  guard let strongSelf = self else {
 '' return
  }
  let model = strongSelf.updateModel(response)
  strongSelf.updateUI(model)
}
複製代碼

不推薦的:

// might crash if self is released before response returns
resource.request().onComplete { [unowned self] response in
  let model = self.updateModel(response)
  self.updateUI(model)
}
複製代碼

不推薦的:

// 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)
}
複製代碼

訪問控制

文章中的全局訪問控制註釋可能會分散主題,這而且不是必要的。然而,適當地使用private和fileprivate,增進了清晰度並促進了封裝。條件容許的話,儘量使用private而不是fileprivate 。使用擴展可能須要您使用fileprivate。

只有當你須要完整的訪問控制規範時才明確的使用open,public及internal。

使用訪問控制做爲首選屬性說明符。訪問控制前惟一的條件是 static符,或屬性例如@IBAction、@IBOutlet和@discardableResult等。

推薦的:

private let message = "Great Scott!"

class TimeMachine {  
  fileprivate dynamic lazy var fluxCapacitor = FluxCapacitor()
}
複製代碼

不推薦的:

fileprivate let message = "Great Scott!"

class TimeMachine {  
  lazy dynamic fileprivate var fluxCapacitor = FluxCapacitor()
}
複製代碼

控制流

儘可能使用for-in風格的for循環而不是while-condition-increment風格。

推薦的:

for _ in 0..<3 {
  print("Hello three times")
}

for (index, person) in attendeeList.enumerated() {
  print("\(person) is at position #\(index)")
}

for index in stride(from: 0, to: items.count, by: 2) {
  print(index)
}

for index in (0...3).reversed() {
  print(index)
}
複製代碼

不推薦的:

var i = 0
while i < 3 {
  print("Hello three times")
  i += 1
}

var i = 0
while i < attendeeList.count {
  let person = attendeeList[i]
  print("\(person) is at position #\(i)")
  i += 1
}
複製代碼

Golden Path

當用條件語句編碼時,左邊的代碼應該是"golden" 或 "happy」路徑。也就是說,不要嵌套if語句。使用多個返回語句也是能夠的,這個guard就是爲此創建的。

推薦的:

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
}
複製代碼

不推薦的:

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
   }
 }
複製代碼


不管使用 guard 仍是 if let 打開多個可選項,在可能的狀況下減小嵌套使用複合版本。例:

推薦的:

guard let number1 = number1,
       let number2 = number2,
       let number3 = number3 else {
   fatalError("impossible")
 }
 // do something with numbers
複製代碼

不推薦的:

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")
 }
複製代碼

失敗的Guard

Guard聲明須要以某種方式退出。通常狀況下,這應該是簡單的一行語句,如return、throw、break、continue和fatalError()。大量代碼的block應該避免使用。若是有多個退出點須要清除代碼,請考慮使用defer(延遲)代碼塊來避免重複清除代碼。

分號

Swift在您的任何一個代碼語句後都不須要分號。若是你想在一行中組合多個語句,則須要‘;’。 但不建議在一行上寫用‘;’分隔的多條語句。

推薦的:

let swift = "not a scripting language"
複製代碼

不推薦的:

let swift = "not a scripting language";
複製代碼

注意:Swift與JavaScript大相徑庭,後者省略分號一般被認爲是不安全的。

括號

if 附近的括號不是必須的,應該省略。 推薦的:

if name == "Hello" {
  print("World")
}
複製代碼

不推薦的:

if (name == "Hello") {
  print("World")
}
複製代碼

在有大量代碼的表達式中,有括號可使代碼更清晰地讀取。

推薦的:

let playerMark = (player == current ? "X" : "O")
複製代碼

機構和 Bundle Identifier

凡是xcode有關的,機構應該設置爲‘Ray Wenderlich’ ,Bundle Identifier應該設置爲com.razeware.TutorialName形式,其中TutorialName是項目的名字。

Swift設計指南原文

相關文章
相關標籤/搜索