Swift 編碼規范(中文)

注:以下皆為翻譯,如有錯誤或疏漏,請指正。謝謝?

簡介

(raywenderlich 版)
您看到的這份規范可能與其他的有所不同,因為這份關注點是web顯示和打印。我們創建這份規范已保證我們書中的代碼,教程和 sdk 一致。

Objective-C 代碼規范:Objective-C Style Guide.

Table of Contents(目錄)

<a id="correctness"></a>

Correctness(正確性)

將 warnings 視為 errors. 比如不要用 ++ 、-- 或 c 風格的 循環 或 string 型的 selectors.

<a id="naming"></a>

Naming(命名)

classes, structures, enumerations and protocols 等類型名字以首字母大寫駝峰命名。變量、方法名以小寫駝峰方式命名

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
}

盡量避免“簡稱”和“縮略詞”,通用縮略詞應該整體大寫整體小寫

Preferred(推薦)

let urlString: URLString
let userID: UserID

Not Preferred(不推薦)

let uRLString: UrlString
let userId: UserId

初始化方法或其他方法,每個參數前都應該有明確說明。

func dateFromString(dateString: String) -> NSDate
func convertPointAt(column column: Int, row: Int) -> CGPoint
func timedAction(afterDelay delay: NSTimeInterval, perform action: SKAction) -> SKAction!

// 調用方式:
dateFromString("2014-03-14")
convertPointAt(column: 42, row: 13)
timedAction(afterDelay: 1.0, perform: someOtherAction)

遵循 Apple 官方習俗為方法命名。

class Counter {
  func combineWith(otherCounter: Counter, options: Dictionary?) { ... }
  func incrementBy(amount: Int) { ... }
}

<a id="protocols"></a>

Protocols(委托)

遵循Apple's API Design Guidelines。 protocols描述某物的應該是名詞。如:Collection, WidgetFactory
protocols是描述能力的應該以-ing, -able或 -ible結尾。如: Equatable, Resizing

<a id="enumerations"></a>

Enumerations(枚舉)

遵循 swift 3 Apple's API Design Guidelines。每個枚舉值用小寫字母開始。

enum Shape {
  case rectangle
  case square
  case rightTriangle
  case equilateralTriangle
}

<a id="prose"></a>

Prose

When referring to functions in prose (tutorials, books, comments) include the required parameter names from the caller's perspective or _ for unnamed parameters. Examples:

Call convertPointAt(column:row:) from your own init implementation.

If you call dateFromString(_:) make sure that you provide a string with the format "yyyy-MM-dd".

If you call timedAction(afterDelay:perform:) from viewDidLoad() remember to provide an adjusted delay value and an action to perform.

You shouldn't call the data source method tableView(_:cellForRowAtIndexPath:) directly.

This is the same as the #selector syntax. When in doubt, look at how Xcode lists the method in the jump bar – our style here matches that.

Methods in Xcode jump bar
Methods in Xcode jump bar

<a id="class-prefixes"></a>

Class Prefixes(Class 前綴)

swift 被自動包含了 module 前綴,你不需要添加額外前綴。如果兩個不同的 module 的兩個名字有沖突,你可以通過在 module 前添加前綴來消除該沖突。當然,僅當 module 名稱可能沖突的時候才為他特定設置一個名稱(這種情況應該很少見)。

import SomeModule

let myClass = MyModule.UsefulClass()

<a id="selectors"></a>

Selectors(選擇器)

Preferred(推薦):

let sel = #selector(viewDidLoad)

Not Preferred(不推薦):

let sel = #selector(ViewController.viewDidLoad)

<a id="generics"></a>

Generics(泛型)

泛型參數應該描述清楚所規定的泛型。當不確定泛型類型是才使用傳統的大寫字母 如T, U, or V表示泛型。

Preferred(推薦):

struct Stack<Element> { ... }
func writeTo<Target: OutputStream>(inout target: Target)
func max<T: Comparable>(x: T, _ y: T) -> T

Not Preferred(不推薦):

struct Stack<T> { ... }
func writeTo<target: OutputStream>(inout t: target)
func max<Thing: Comparable>(x: Thing, _ y: Thing) -> Thing

<a id="language"></a>

Language(語言)

用美式英語拼寫才符合Apple's API

Preferred(推薦):

let color = "red"

Not Preferred(不推薦):

let colour = "red"

<a id="code-organization"></a>

Code Organization(代碼組織)

Use extensions to organize your code into logical blocks of functionality. Each extension should be set off with a // MARK: - comment to keep things well-organized.

<a id="protocol-conformance"></a>

Protocol Conformance

每個 protocol 的實現分別對應一個 extension 的方式實現。

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
}

由于編譯器不允許一個類重新申明 protocol 實現,因此,不是一直要求每個 protocols 都要分離出一個 extension.特別是當該 class 是個最終 class 或該 class 方法很少。

對UIKit view controllers,將lifecycle, custom accessors, and IBAction等方法單獨放在一個 class extension 中。

<a id="unused-code"></a>

Unused Code(無用代碼)

無用的code ,包括 Xcode 注釋都應該被移除。空方法應該被移除。

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
}

<a id="minimal-imports"></a>

Minimal Imports(最少引入)

不要引用UIKitFoundation

<a id="spacing"></a>

Spacing(空格)

  • 用4個空格而不是 tabs調整對其,xcode 中設置如下:
Xcode indent settings
Xcode indent settings
Xcode Project settings
Xcode Project settings
  • 用命令Ctr+I重新排列代碼。

Preferred(推薦):

if user.isHappy {
  // Do something
} else {
  // Do something else
}

Not Preferred(不推薦):

if user.isHappy
{
  // Do something
}
else {
  // Do something else
}
  • 每兩個方法之間應該空一行。

  • 冒號左邊無空格,右邊有一個空格。? : 和 空字典[:] 例外

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

<a id="comments"></a>

Comments(注釋)

必要的時候添加注釋注明 為什么

避免一大段注釋解釋一行代碼,代碼應該有自我解釋性Exception: This does not apply to those comments used to generate documentation.

<a id="classes-and-structures"></a>

Classes and Structures(類和結構體)

<a id="which-one-to-use"></a>

用哪一個?

struct 是值類型,當你不需要唯一身份的時候用struct.arrayOne = [a, b, c] 和arrayTwo = [a, b, c]意義是一樣的。不需要在乎他們是否是同一個 array。這就是用 struct 的原因。

class 是引用類型。需要唯一標識或有生命周期的才用 class。你可以把一個人定義為一個 class 實例,因為每個人都是不一樣的。僅僅只有名字和生日相同的兩人也不是同一個人。

有時候,應該是 struct 確是 class ,如以下情況 NSDate NSSet

<a id="example-definition"></a>

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

以上示例演示遵循如下指導:

  • 聲明的類型用: 銜接在后面
  • 多個變量公用 關鍵字 var let 可以聲明在同一行
  • 縮進 getter setter willSet didSet
  • 變量前不需要添加 internal關鍵字,因為這是默認的。同樣不需要重復寫重寫方法內的代碼

<a id="use-of-self"></a>

Use of Self(self 用法)

避免使用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)
    }
  }
}

<a id="computed-properties"></a>

Computed Properties(計算屬性)

如果一個計算屬性的返回值很簡單,可以省去 get{}。有 set{} 就一定要有 get{}

Preferred(推薦):

var diameter: Double {
  return radius * 2
}

Not Preferred(不推薦):

var diameter: Double {
  get {
    return radius * 2
  }
}

<a id="final"></a>

Final(關鍵字)

當你不希望該類被繼承時,用 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
  }
}

<a id="function-declarations"></a>

Function Declarations(函數聲明)

保持短方法名在一行

func reticulateSplines(spline: [Double]) -> Bool {
  // reticulate code goes here
}

如果方法名換行,下一行添加一個縮進距離

func reticulateSplines(spline: [Double], adjustmentFactor: Double,
    translateConstant: Int, comment: String) -> Bool {
  // reticulate code goes here
}

<a id="closure-expressions"></a>

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

對單行的閉包,返回值可以用含蓄方式

attendeeList.sort { a, b in
  a > b
}

鏈式方法后面跟隨閉包,方法名應該清晰明了。

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

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

<a id="types"></a>

Types(類型)

如無必要,用 swift 類型,不要用 oc 類型

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以便代碼更簡明,避免各種轉換。

<a id="constants"></a>

Constants

常量用 let,變量用var

Tip: A good technique is to define everything using let and only change it to var if the compiler complains!

你可以將常量定義到一個類型中而不是作為實例的類型屬性。 類型屬性用static let

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?

<a id="static-methods-and-variable-type-properties"></a>

Static Methods and Variable Type Properties

static方法和類型的功能類似全局函數和全局變量

<a id="optionals"></a>

Optionals(可選值)

聲明一個函數的某個參數可以為 nil 時,用?

當你確定某個變量在使用時已經確定不是 nil 時,在后面加!

......

self.textContainer?.textLabel?.setNeedsDisplay()

Use optional binding when it's more convenient to unwrap once and perform multiple operations:

if let textContainer = self.textContainer {
  // do many things with textContainer
}

命名一個 optional 變量,避免使用optionalStringmaybeView,因為他們已經在聲明時體現出來了。

optional 綁定,使用原始名稱而不是unwrappedViewactualLabel

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

<a id="struct-initializers"></a>

Struct Initializers(結構體初始化)

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)

Prefer the struct-scope constants CGRect.infinite, CGRect.null, etc. over global constants CGRectInfinite, CGRectNull, etc. For existing variables, you can use the shorter .zero.

<a id="lazy-initialization"></a>

Lazy Initialization(懶加載)

Consider using lazy initialization for finer grain control over object lifetime. This is especially true for UIViewController that loads views lazily. You can either use a closure that is immediately called { }() or call a private factory method. Example:

用懶初始化

lazy var locationManager: CLLocationManager = self.makeLocationManager()

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

Notes:

  • [unowned self] is not required here. A retain cycle is not created.
  • Location manager has a side-effect for popping up UI to ask the user for permission so fine grain control makes sense here.
  • 不需要使用[unowned self],因為沒有創建循環引用
  • ......

<a id="type-inference"></a>

Type Inference(類型)

Prefer compact code and let the compiler infer the type for constants or variables of single instances. Type inference is also appropriate for small (non-empty) arrays and dictionaries. When required, specify the specific type such as CGFloat or Int16.

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]()

<a id="type-annotation-for-empty-arrays-and-dictionaries"></a>

Type Annotation for Empty Arrays and Dictionaries

用以下方式創建 array dictionary

Preferred(推薦):

var names: [String] = []
var lookup: [String: Int] = [:]

Not Preferred(不推薦):

var names = [String]()
var lookup = [String: Int]()

NOTE: Following this guideline means picking descriptive names is even more important than before.

<a id="syntactic-sugar"></a>

Syntactic Sugar(語法竅門)

推薦用短語義聲明

Preferred(推薦):

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

Not Preferred(不推薦):

var deviceModels: Array<String>
var employees: Dictionary<Int, String>
var faxNumber: Optional<Int>

<a id="functions-vs-methods"></a>

Functions vs Methods(函數 方法)

class 或類型以外的自由函數應該單獨使用。如果可以,用方法而不是函數。這樣會更容易獲得和發現。

自由函數不應該跟任何類型或實例關聯。

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

<a id="memory-management"></a>

Memory Management(內存管理)

代碼盡量避免循環引用,避免強引用,用weakunowned引用。用值類型避免循環引用。

<a id="extending-lifetime"></a>

Extending object lifetime(擴展生命周期)

[weak self]guard let strongSelf = self else { return } 模式擴展生命周期。用[weak self][unowned self]更好。

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

<a id="access-control"></a>

Access Control(訪問控制)

Full access control annotation in tutorials can distract from the main topic and is not required. Using private appropriately, however, adds clarity and promotes encapsulation. Use private as the leading property specifier. The only things that should come before access control are the static specifier or attributes such as @IBAction and @IBOutlet.

......

Preferred(推薦):

class TimeMachine {  
  private dynamic lazy var fluxCapacitor = FluxCapacitor()
}

Not Preferred(不推薦):

class TimeMachine {  
  lazy dynamic private var fluxCapacitor = FluxCapacitor()
}

<a id="control-flow"></a>

Control Flow(控制流)

更推薦 for-in 而不是 while-condition-increment

Preferred(推薦):

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

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

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

for index in (0...3).reverse() {
  print(index)
}

Not Preferred(不推薦):

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
}

<a id="golden-path"></a>

Golden Path(黃金路線)

多個條件判斷時,不要多個if嵌套,用 guard

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

判斷多個 optional 時,用 guardif let。減少嵌套。

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

<a id="failing-guards"></a>

Failing Guards

Guard 要求以某種方式退出。一般可以用 return, throw, break, continue, and fatalError()。避免大量代碼

<a id="semicolons"></a>

Semicolons(分號)

swift 不要求寫;,僅僅在你把多個語句寫在同一行時才要求;

不要把多個狀態語句寫在同一行

for-conditional-increment才會將多個語句寫在同一行。但是 swift 推薦用for-in

Preferred(推薦):

let swift = "not a scripting language"

Not Preferred(不推薦):

let swift = "not a scripting language";

NOTE: generally considered unsafe

<a id="parentheses"></a>

Parentheses(圓括號)

圓括號也不是必須的

Preferred(推薦):

if name == "Hello" {
  print("World")
}

Not Preferred(不推薦):

if (name == "Hello") {
  print("World")
}

<a id="smiley-face"></a>

Smiley Face(笑臉)

笑臉的正確表示方法

Preferred(推薦):

:]

Not Preferred(不推薦):

:)
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 從其他地方整理了一些編碼規范的資料,分享給大家。YoY 1.語言(Language) 需要使用US English...
    大臉貓121閱讀 447評論 0 2
  • **2014真題Directions:Read the following text. Choose the be...
    又是夜半驚坐起閱讀 9,934評論 0 23
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,991評論 19 139
  • 過去已經過去,未來還未來。 生命最好的期待,這一刻展開。 當過去已經成為過去式,未來還沒有到來。 生命最好的期待,...
    瘋狂吧_e0ca閱讀 2,276評論 1 2
  • 不知不覺已邁入三十歲了,以前總在幻想三十歲的樣子,職場白領,有美麗的妻子,有個女兒,然而現實總讓人大跌眼鏡...
    canhe98閱讀 333評論 1 2