該文章由 iOSCaff 社區 組織翻譯,後續社區會保持文章的更新,若是你以爲這篇文章對你有幫助,歡迎到社區點贊支持。javascript
譯文地址:https://ioscaff.com/topics/84/raywenderlich-official-swift-style-guidehtml
原文地址:https://github.com/raywenderlich/swift-style-guidejava
這篇風格指南可能不一樣於你看到的其餘風格指南。由於它的重點偏向於打印和網頁的可讀性。咱們建立這篇風格指南的目的,是爲了讓咱們的書、教程和初學者套件中的代碼,在有不少做者同時寫書的狀況下,也能保持規範與一致。ios
咱們的首要目標是清晰、一致和簡潔。git
努力讓你的代碼在沒有警告的狀況下編譯。 這條規則決定了許多風格決策,好比使用 #selector
類型而不是字符串字面量。github
描述性和一致性的命名讓軟件更易於閱讀和理解。使用 API 設計規範 中描述的 Swift 命名規範。 一些關鍵點包括以下:express
make
開頭在文章中引用方法時,含義明確是相當重要的。儘量用最簡單的形式引用方法。編程
addTarget
。addTarget(_:action:)
。addTarget(_: Any?, action: Selector?)
。用上面的例子使用 UIGestureRecognizer
, 1 是明確的,也是首選的。swift
專家提示: 你能夠用 Xcode 的跳轉欄來查看帶有參數標籤的方法。api
Swift 的類自動被包含在模塊分配的命名空間中。不該該再添加相似於 RW 的類前綴。若是不一樣模塊的兩個命名衝突,能夠在類名前添加模塊名來消除歧義。不管如何,僅在少數可能引發混淆的狀況下指明模塊名。
import SomeModule
let myClass = MyModule.UsefulClass()
複製代碼
當建立自定義代理方法的時候,未命名的第一個參數應該是代理源。 ( UIKit 包含不少這樣的例子。)
推薦:
func namePickerView(_ namePickerView: NamePickerView, didSelectName name: String)
func namePickerViewShouldReload(_ namePickerView: NamePickerView) -> Bool
複製代碼
不推薦:
func didSelectName(namePicker: NamePickerViewController, name: String)
func namePickerShouldReload() -> Bool
複製代碼
使用上下文推斷編譯器書寫更短更明確的代碼。(你也能夠閱讀 類型推斷。)
推薦:
let selector = #selector(viewDidLoad)
view.backgroundColor = .red
let toView = context.view(forKey: .to)
let view = UIView(frame: .zero)
複製代碼
不推薦:
let selector = #selector(ViewController.viewDidLoad)
view.backgroundColor = UIColor.red
let toView = context.view(forKey: UITransitionContextViewKey.to)
let view = UIView(frame: CGRect.zero)
複製代碼
通常的類型參數應該是描述性的、大寫駝峯法命名。當類名沒有富有含義的關係或角色時,使用傳統的單個大寫字母來命名,例如 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)
複製代碼
使用美式英語拼寫來匹配 Apple 的 API。
推薦:
let color = "red"
複製代碼
不推薦:
let colour = "red"
複製代碼
用擴展將代碼組織爲功能邏輯塊。每一個擴展都應該添加 // MARK: -
註釋,以保證代碼的結構清晰。
推薦爲協議方法加一個單獨的擴展,尤爲是爲一個模型加入協議遵循的時候。這可讓有關聯的協議方法被分組在一塊兒,也能夠簡化用類關聯方法向這個類添加協議的指令。
推薦:
class MyViewController: UIViewController {
// 類填充在這
}
// MARK: - UITableViewDataSource
extension MyViewController: UITableViewDataSource {
// table view 的數據源方法
}
// MARK: - UIScrollViewDelegate
extension MyViewController: UIScrollViewDelegate {
// scroll view 的代理方法
}
複製代碼
不推薦:
class MyViewController: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
// 全部方法
}
複製代碼
由於編譯器不容許在派生類中從新聲明協議遵循,因此並不老是須要複製基類的擴展組。若是派生類是一個終端類,而且只有少數方法會被覆蓋,那麼這個原則尤其正確。應由做者自行決定什麼時候保留擴展組- 。
對於 UIKit 中的視圖控制器,可考慮將生命週期、自定義存取器和 IBAction 分組在單獨的類擴展中。
無用代碼(殭屍代碼),包括 Xcode 模板代碼和佔位註釋,應該被移除掉。教程或書籍中教用戶使用的註釋代碼除外。
僅實現簡單調用父類,但與教程無直接關聯的方法應該被移除。這裏包括任何爲空的或無用的 UIApplicationDelegate 方法。
推薦:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Database.contacts.count
}
複製代碼
不推薦:
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// 任何能夠重建資源的處理。
}
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning 未完成的實現,返回節數。
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning 未完成的實現,返回行數。
return Database.contacts.count
}
複製代碼
引用最小化。舉個例子,引用 Foundation
就足夠的狀況下不要再引用 UIKit
。
if
/ else
/ switch
/ while
等)老是在和語句相同的行寫左括號,而在新行寫右括號。if user.isHappy {
// 作一件事
} else {
// 作另外一件事
}
複製代碼
不推薦:
if user.isHappy
{
// 作一件事
}
else {
// 作另外一件事
}
複製代碼
? :
、空字典 [:]
和帶有未命名參數 (_:)
的 #selector
語法 .推薦:
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]
}
複製代碼
須要的時候,用註釋來解釋一個特定的代碼片斷 爲何 作某件事。註釋應保持要麼是最新的,要麼就被刪除。
爲了不塊註釋和代碼內聯,代碼應該儘量自文檔化。 例外:這不含那些註釋被用於生成文檔的狀況 。
請記住,結構體有 值語義。對沒有標識的事物應用結構體。一個包含 [a, b, c] 的數組和另外一個包含 [a, b, c] 的數組是徹底同樣的。他們是能夠徹底互換的。使用第一個數組仍是第二個數組都無所謂,由於他們表明着徹底相同的事物。這就是爲何數組是結構體。
類有 引用語義。對有標識或有具體生命週期的事物應用類。你須要將人建模爲一個類,由於不一樣兩我的對象是兩個不一樣的事物。只是由於兩我的擁有相同的名字和生日不意味着他們是同一我的。可是人的生日應該是一個結構體,由於 1950 年 3 月 3 日和任何其它的 1950 年 3 月 3 日日期對象是相同的。日期自己沒有標識。
有時,事物應該是結構體但須要遵循 AnyObject
,或在歷史上已經被建模爲類 (NSDate
、 NSSet
)。儘量嘗試遵循這些原則。
這是一個風格良好的類定義例子:
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
。internal
的默認修飾符。相似的,當重寫一個方法時,不要再重複添加訪問修飾符。centerString
在擴展中使用 private
訪問控制。爲了簡潔,請避免使用 self
關鍵詞,Swift 不須要用它來訪問一個對象屬性或調用它的方法。
僅在編譯器須要時(在 @escaping
閉包或初始化函數中,消除參數與屬性的歧義)才使用 self。換句話說,若是不須要 self
就能編譯經過,則能夠忽略它。
爲了簡潔,若是一個計算屬性是隻讀的,則能夠忽略 get 子句。僅在提供了 set 子句的狀況下才須要 get 子句。
推薦:
var diameter: Double {
return radius * 2
}
複製代碼
不推薦:
var diameter: Double {
get {
return radius * 2
}
}
複製代碼
在教程中將類或成員標記爲 final
會從主題分散注意力,並且也不必。 儘管如此,final
的使用有時能夠代表你的意圖,且值得你這樣作。在下面的例子中,Box
有特定的目的,且並不打算在派生類中自定義它。標記爲 final
可使它更清晰。
// 用這個 Box 類將任何通常類型轉換爲引用類型。
final class Box<T> {
let value: T
init(_ value: T) {
self.value = value
}
}
複製代碼
在一行中保持較短的方法聲明,包括左括號:
func reticulateSplines(spline: [Double]) -> Bool {
// 在這裏寫網格代碼
}
複製代碼
對於簽名較長的函數,則需在合適的位置換行,而後在後續的行中加一個額外的換行:
func reticulateSplines(spline: [Double], adjustmentFactor: Double, translateConstant: Int, comment: String) -> Bool {
// 在這裏寫網絡代碼
}
複製代碼
僅在參數列表最後有個單獨的閉包表達式參數時,使用尾隨閉包語法。給閉包參數定義一個描述性的命名。
推薦:
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
複製代碼
在 Sprite Kit 代碼中,使用 CGFloat
可讓你的代碼避免太多轉換,從而讓你的代碼更加簡潔。
使用 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 // 污染全局命名空間
let root2 = 1.41421356237309504880168872
let hypotenuse = side * root2 // 什麼 root2?
複製代碼
靜態方法和類型屬性跟全局函數和全局變量的工做原理相似,應當謹慎使用。當功能的做用域是一個特定類型或須要與 Objective-C 交互時,它們很是有用。
在可接受 nil 值的狀況下,使用 ?
聲明變量和函數返回類型爲可選類型。
用 !
聲明的隱式解包類型,僅用於稍後在使用前初始化的實例變量,好比將在 viewDidLoad
中建立子視圖。
當訪問一個可選值時,若是值僅被訪問一次或在鏈中有許多可選項時,使用可選鏈:
self.textContainer?.textLabel?.setNeedsDisplay()
複製代碼
當一次性解包和執行多個操做更方便時,使用可選綁定:
if let textContainer = self.textContainer {
// 用 textContainer 作不少事情
}
複製代碼
在命名可選變量和屬性時,需避免相似 optionalString
或 maybeView
這樣的命名,由於他們的可選性已經體如今類型聲明中了。
對於可選綁定,適當時使用原始名稱,而不是使用像 unwrappedView
或 actualLabel
這樣的名稱。
推薦:
var subview: UIView?
var volume: Double?
// later on...
if let subview = subview, let volume = volume {
// 使用展開的 subview 和 volume 作某件事
}
複製代碼
不推薦:
var optionalSubview: UIView?
var volume: Double?
if let unwrappedSubview = optionalSubview {
if let realVolume = volume {
// 使用 unwrappedSubview 和 volume 作某件事
}
}
複製代碼
在更細粒度地控制對象聲明週期時考慮使用延遲初始化。 對於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]
。優先選擇簡潔緊湊的代碼,讓編譯器爲單個實例的常量或變量推斷類型。類型推斷也適合於小(非空)的數組和字典。須要時,請指明特定類型,如 CGFloat
或 Int16
。
推薦:
let message = "Click the button"
let currentBounds = computeViewBounds()
var names = ["Mic", "Sam", "Christine"]
let maximumWidth: CGFloat = 106.5
複製代碼
不推薦:
let message: String = "Click the button"
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() // 容易領悟的
rocket.launch() // 模型的行爲
複製代碼
不推薦:
let sorted = mergeSort(items) // 難以領悟的
launch(&rocket)
複製代碼
自由函數異常
let tuples = zip(a, b) // 做爲自由函數感到天然(對稱)
let value = max(x, y, z) // 另外一個感到天然的自由函數
複製代碼
代碼 (甚至非生產環境、教程演示的代碼)都不該該出現循環引用。分析你的對象圖並用 weak
和 unowned
來防止強循環引用。或者,使用值類型( struct
、enum
)來完全防止循環引用。
使用慣用語法 [weak self]
和 guard let strongSelf = self else { return }
來延長對象的生命週期。 在 self
超出閉包生命週期不明顯的地方,[weak self]
更優於 [unowned self]
。 明確地延長生命週期優於可選解包。
推薦:
resource.request().onComplete { [weak self] response in
guard let strongSelf = self else {
return
}
let model = strongSelf.updateModel(response)
strongSelf.updateUI(model)
}
複製代碼
不推薦:
// 若是在響應返回前 self 被釋放,則可能致使崩潰
resource.request().onComplete { [unowned self] response in
let model = self.updateModel(response)
self.updateUI(model)
}
複製代碼
不推薦:
// 內存回收能夠發生在更新模型和更新 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
循環的 for-in
格式而不是 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
}
複製代碼
當使用條件語句編碼時,代碼的左邊距應該是 「黃金」或「快樂」的路徑。就是不要嵌套 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
}
// 用上下文和輸入計算頻率
return frequencies
}
複製代碼
不推薦:
func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
if let context = context {
if let inputData = inputData {
// 用上下文和輸入計算頻率
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")
}
// 用數字作某事
複製代碼
不推薦:
if let number1 = number1 {
if let number2 = number2 {
if let number3 = number3 {
// 用數字作某事
} else {
fatalError("impossible")
}
} else {
fatalError("impossible")
}
} else {
fatalError("impossible")
}
複製代碼
對於用某些方法退出,防禦語句是必要的。通常地,它應該是一行簡潔的語句,好比: return
、 throw
、 break
、 continue
和 fatalError()
。應該避免大的代碼塊。若是清理代碼被用在多個退出點,則能夠考慮用 defer
塊來避免清理代碼的重複。
在 Swift 中,每條代碼語句後面都不須要加分號。只有在你但願在一行中結合多條語句,才須要加分號。
不要在用分號分隔的單行中寫多條語句。
推薦:
let swift = "not a scripting language"
複製代碼
不推薦:
let swift = "not a scripting language";
複製代碼
注:Swift 很是不一樣於 JavaScript。在 JavaScript 中忽略分號 通常被認爲不安全。
條件周圍的括號是沒必要要的,應該被忽略。
推薦:
if name == "Hello" {
print("World")
}
複製代碼
不推薦:
if (name == "Hello") {
print("World")
}
複製代碼
在更大的表達式中,可選括號有時可讓代碼讀起來更清晰。
推薦:
let playerMark = (player == current ? "X" : "O")
複製代碼
涉及到 Xcode 項目的地方,組織應該被設置爲 Ray Wenderlich
而且包 ID 應該被設置爲 com.razeware.TutorialName
,其中 TutorialName
是教程項目的名字。
如下版權聲明應該被包含在每一個源文件的頂部:
/// Copyright (c) 2018 Razeware LLC
///
/// Permission is hereby granted, free of charge, to any person obtaining a copy
/// of this software and associated documentation files (the "Software"), to deal
/// in the Software without restriction, including without limitation the rights
/// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
/// copies of the Software, and to permit persons to whom the Software is
/// furnished to do so, subject to the following conditions:
///
/// The above copyright notice and this permission notice shall be included in
/// all copies or substantial portions of the Software.
///
/// Notwithstanding the foregoing, you may not use, copy, modify, merge, publish,
/// distribute, sublicense, create a derivative work, and/or sell copies of the
/// Software in any work that is designed, intended, or marketed for pedagogical or
/// instructional purposes related to programming, coding, application development,
/// or information technology. Permission for such use, copying, modification,
/// merger, publication, distribution, sublicensing, creation of derivative works,
/// or sale is expressly withheld.
///
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
/// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
/// THE SOFTWARE.
複製代碼
笑臉是網站 raywenderlich.com 很是突出的風格特色!正確使用微笑來表達對編碼主題的歡樂與興奮是很是重要的。使用右方括號 ]
是由於它表明 ASCII 中的最大笑容。右括號 )
表示三心二意的笑臉,所以不推薦使用。
推薦:
:]
複製代碼
不推薦:
:)
複製代碼
iOSCaff 是一個面向 iOS 開發者的技術知識社區,致力於爲開發者提供一個更加高效、便捷的學習環境。