花了周末的時間翻譯raywenderlich.com的Swift編碼規(guī)范(傳送門), 原文是針對寫作時Swift代碼的規(guī)范,在整理過程中刪除了部分和實際項目無關的條目。
編碼規(guī)范的目標是保證代碼的簡潔性,可讀性和可維護性。
正確性
把警告當做錯誤處理。這條規(guī)則從根本禁止了一些文法使用,如推薦使用#selector文而不是用字符串(更多請閱讀Swift 3為什么推薦使用#selector)。?
命名
使用具有描述性的名稱,并結合駝峰式命名規(guī)則給類方法和變量等命名。類別名稱(類,結構體,枚舉和協(xié)議)首字母大寫,而方法或者變量的首字母小寫。更多命名規(guī)范請查閱Apple Design Guidelines。
正例
private let maximumWidgetCount = 100
class WidgetContainer {
? ? var widgetButton: UIButton ?
? ? let ?widgetHeightPercentage = 0.85
}
反例
let MAX_WIDGET_COUNT = 100
class app_widgetContainer {
? ?var wBut: UIButton
? ?let ?wHeightPct = 0.85
}
縮寫和簡寫應該要盡量避免,遵守蘋果命名規(guī)范,縮寫和簡寫中的所有字符大小寫要一致。
正例
let urlString: ?URLString
let userID: ?UserID
反例
let uRLString: ?UrlString
let userId: ?UserId
對于函數(shù)和初始化方法,建議給所有參數(shù)命名,除非上下文非常清晰,如果結合外部參數(shù)命名可以讓函數(shù)調(diào)用更易讀,要結合起來。
func dateFromString(dateString: String) -> NSDate
func convertPointAt(column column:Int, row:Int) > CGPoint
func timedAction(afterDelay delay: NSTime Interval,performaction: SKAction) ->SKAction!
//調(diào)用上述函數(shù)時如下:
dateFromString("2014-03-14")
convertPointAt(column: 42, row: 13)
timedAction(afterDelay: 1.0, perform: someOtherAction)
方法命名的時候要考慮方法體中的第一個參數(shù)的名稱。
class Counter {
? //combineWith + otherCounter
? func combineWith(otherCounter:? Counter, options: Dictionary?) { ... }
?//incrementBy + amount
? func incrementBy(amount: Int) { ... }
}
協(xié)議
根據(jù)蘋果接口設計指導準則,協(xié)議名稱用來描述一些東西是什么的時候是名詞,例如:Collection,WidgetFactory。若協(xié)議名稱用來描述能力應該以-ing, -able, ?或 -ible結尾,例如:Equatable,Resizing.
枚舉
根據(jù)Swift 3蘋果接口設計指導文檔,枚舉中的值使用“小駱駝拼寫法”命名規(guī)則
enum Shape {
? ? case rectangle
? ? case square
? ? case ?triangle
? ? case ?circle
}
類前綴?
Swift中類別(類,結構體)在編譯時會把模塊設置為默認的命名空間,所以不用為了區(qū)分類別而添加前綴,比如RW。如果擔心來自不同模塊的兩個名稱發(fā)生沖突,可以在使用時添加模塊名稱來區(qū)分,注意不要濫用模塊名稱,僅在有可能發(fā)生沖突或疑惑的場景下使用。
import SomeModule
let myClass = MyModule.UsefulClass()
委托 - Delegate
在定義委托方法時,第一個未命名參數(shù)應是委托數(shù)據(jù)源。 (為了保持參數(shù)聲明的一致性在Swift3引入的)
正例
func namePickerView(_ namePickerView: NamePickerView, didSelectName name: String)
func namePickerViewShouldReload(_ namePickerView: NamePickerView) -> Bool
反例
func didSelectName(namePicker: NamePickerViewController, name: String)
func namePickerShouldReload() -> Bool
泛型
泛型類參數(shù)應具有描述性,遵守“大駱駝命名法”。如果一個參數(shù)名沒有具體的含義,可以使用傳統(tǒng)單大寫字符,如T, ?U, 或V等。
正例
struct Stack<Element>{ ... }
func writeTo <Target: OutputStream>(to Target: inout Target)
func swap(_ 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)
語言
使用美式英語來定義接口。
正例
let color = "red"
反例
let colour = "red"
代碼組織結構
協(xié)議一致性
當一個對象要實現(xiàn)協(xié)議一致性時,推薦使用 extension 隔離協(xié)議中的方法集,這樣讓相關方法和協(xié)議集中顯示在一起,?也簡化了類支持一個協(xié)議和實現(xiàn)相關方法的流程。
正例
class MyViewcontroller: UIViewController {
? ? ? // ?方法
}
extension MyViewcontroller: UITableViewDataSource {
? ? ? // UITableViewDataSource 方法
}
extension MyViewcontroller: UIScrollViewDelegate {
? ? ? // UIScrollViewDelegate 方法
}?
反例
class MyViewcontroller: UIViewController, UITableViewDataSource, UIScrollViewDelegate {
? ? ? ?// 所有的方法
}
對于UIKit view controllers, 建議用extensions定義不同的類,按照生命周期,自定義方法,IBAction分組。
無用代碼
無用的代碼,包括Xcode生成的模板代碼和占位符注釋應該刪除,除非是有目的性的保留這些代碼。
一些方法內(nèi)只是簡單地調(diào)用了父類里面的方法也需要刪除,包括UIApplicationDelegate內(nèi)的空方法和無用方法。
正例
override func tableView(tableView: UITableView,numberOfRowsInSectionsection: Int) -> Int{
? return Database.contacts.count
}
反例
override func didReceiveMemoryWarning() {
? super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
? return 1
}
override func tableView(tableView: UITableView,numberOfRowsInSectionsection: Int) -> Int {
? return Database.contacts.count
}
最少引入
減少不必要的引入,例如引入Foundation能滿足的情況下不用引入UIKit。
間隔
縮進使用2個空格. 在Xcode的偏好設置和項目設置都可以配置。
方法體的大括號和其他大括號 (if/else/switch/while等) 首括號和首行語句在同一行,尾括號新起一行。
正例
if user.isHappy {
? // xxoo
} else {
? // ooxx
}
反例
if user.isHappy
{
? // xxoo
}
else{
? // ooxx
}
方法體內(nèi)最多只有一個空行,這樣可以保持整齊和簡潔,如果需要多個空行,說明代碼需要重構。
: 左面不要空格,而右面需要保留空格,例外的是三元操作符 ? : 和空數(shù)組 [:]
正例
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]
}
注釋
當需要的時候,使用注釋來解釋闡明特定一塊代碼的用途,注釋必須保持最新,過期的注釋要及時刪除。
避免代碼中出現(xiàn)注釋塊,代碼本身應盡量起到注釋的作用,如果注釋是為了生成文檔可以例外。
類和結構體
應該使用哪一個
結構體是值類型。結構體在使用中沒有標識。一個數(shù)組包含[a, b, c]和另外一個數(shù)組包含[a, b, c]是完全一樣的,它們完全可以?互相替換,使用第一個還是使用第二個都一樣,因為它們代表的是同一個東西。這就是為什么數(shù)組是結構體。
類是引用類型。類使用的場景是需要一個標識或者需要一個特定的生命周期。假設你需要對人?抽象為一個類,因為兩個人,是兩個不同的東西。即使兩個人有同樣的名字和生日,也不能確定這兩個人是一樣的。但是人的生日是一個結構體,因為日期1950/03/03和另外一個日期1950/03/03是相同的,日期是結構體沒有唯一標示。
有時,一些事物應該定義為結構體,但是還需要兼容AnyObject或者已經(jīng)在以前的歷史版本中定義為類(NSDate,NSSet),所以盡可能的注意?類和結構體之間的區(qū)別。
類定義
下面是一個設計良好的類定義:
class Circle: Shape {
? var x: Int, y: Int
? var radius: Double
? var diameter: Double{
? ? ? get {
? ? ? ? returnradius * 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))"
? }
}
上面的例子展示了下面的設計準則:
屬性,變量,常量和參數(shù)等在聲明定義時,其中: 符號后有空格,而: 符號前沒有空格,比如x: Int, 和Circle: Shape
如果定義多個變量/數(shù)據(jù)結構時出于相同的目的和上下文,可以定義在同一行。
縮進getter,setter的定義和屬性觀察器的定義。
不需要添加internal這樣的默認的修飾符。也不需要在重寫一個方法時添加訪問修飾符。
self的使用
為了保持簡潔,可以避免使用 self 關鍵詞,因為Swift 在訪問對象屬性和調(diào)用對象方法不需要使用 self。
不過當在構造器中需要區(qū)分屬性名和參數(shù)名時需要使用 self,還有當在在閉包表達式中引用屬性值。
class BoardLocation ?{
? ? let row: Int, column: Int
? ? init(row: Int, column: Int) ?{
? ? ? ? self.row = row
? ? ? ? self.column = column
? ? ? ? let closure = {
? ? ? ? ? ? print(self.row)? ?
? ? ? ? ?}?
? ? }
}
計算屬性
為了保持簡潔,如果一個屬性是只讀的,請忽略掉get語句。因為只有在需要定義set語句的時候,才需要get語句。
正例
var diameter: ?Double{
? ? return radius ?* ?2
}
反例
var diameter: ?Double{
? ? ?get {
? ? ? ? return radius * 2
? ? ?}
}
?Final
給那些不打算被繼承的類使用final 修飾符, ?例如:
final class Box<T> {
? ? ?let value: T
? ? ?init(_ value: T) {
? ? ? ? ?self.value = value?
? ? ?}
}
?函數(shù)聲明
在定義短函數(shù)聲明時保持在一行,一行內(nèi)包括頭括號:
func reticulateSplines(spline: [Double]) -> Bool {
? ? ?// xxoo
}
對于聲明較長的函數(shù)時,在適當?shù)奈恢脫Q行并在第二行多添加一個縮進:
func reticulateSplines(spline: [Double], adjustmentFactor: Double,? ? translateConstant:Int, ? ? ? ? ?comment:String) -> Bool {
? ? ? // ooxx
}
閉包表達式
僅在閉包表達式參數(shù)在參數(shù)列表中最后一個時,使用尾隨閉包表達式。閉包參數(shù)命名要有描述性。
正例
UIView.animateWithDuration(1.0) {
? ? ?self.myView.alpha=0
}
UIView.animateWithDuration(1.0,?
? ? ? animations: {
? ? ? ? ? ? self.myView.alpha = 0
? ? ? ?},?
? ? ? ? completion: { finish in
? ? ? ? ? ? ?self.myView.removeFromSuperview()?
? ?}
)
反例
UIView.animateWithDuration(1.0, animations: {
? ? ? self.myView.alpha=0
})
UIView.animateWithDuration(1.0,?
? ? ? ? animations: {
? ? ? ? ? ? ? self.myView.alpha = 0
? ? ? ? ? ?}) { finish in
? ? ? ? ? self.myView.removeFromSuperview()
}
當單個閉包表達式上下文清晰時,使用隱式的返回值:
attendeeList.sort { a, b in
? ? ?a > b
}
鏈式方法使用尾隨閉包會更清晰易讀,?至于如何使用空格,換行,還是使用命名和匿名參數(shù)不做具體要求。
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}
類型
優(yōu)先使用Swift原生類型,可以根據(jù)需要使用Objective-C提供的方法,因為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可以避免數(shù)據(jù)多次轉換,讓代碼更簡潔。
常量
定義常量使用 let 關鍵字,定義變量使用 var 關鍵字, 如果變量的值未來不會發(fā)生變化要使用常量。
?建議: 一個好的技巧是,使用 let 定義任何東西,只有在編譯器提出警告需要改變的時候才修改為 var 定義。
你可以使用類型屬性來定義類型常量而不是實例常量,使用static let 可以定義類型屬性常量。 這樣方式定義類別屬性整體上優(yōu)于全局常量,因為更容易區(qū)分于實例屬性. ?比如:
正例
enum Math {
? ?static let e = 2.718281828459045235360287 ?
? ?static let pi = 3.141592653589793238462643
}
radius * Math.pi * 2// ?周長
Note:?使用枚舉的好處是變量不會被無意初始化,且全局有效。
反例
let e=2.718281828459045235360287// ?污染全局命名空間
let pi=3.141592653589793238462643
radius * pi * 2// pi是實例數(shù)據(jù)還是全局常量?
靜態(tài)方法和變量類型屬性
靜態(tài)方法和類別屬性的工作原理類似于全局方法和全局屬性,應該節(jié)制使用。它們的使用場景在于如果某些功能局限于特別的類型或和Objective-C 互相調(diào)用。
可選類型
可以變量和函數(shù)返回值聲明為可選類型(?),如果nil值可以接受。
當你確認實例變量會稍晚在使用前初始化,可以在聲明時使用!來隱式的拆包類型,比如在viewDidLoad中會初始化的子視圖。
當你訪問一個可選類型值時,如果只需要訪問一次或者在可選值鏈中有多個可選類型值時,請使用可選類型值鏈:
self.textContainer?.textLabel?.setNeedsDisplay()
如果可以方便的一次性拆包或者?執(zhí)行多次性操作時,使用可選類型綁定:
if let textContainer =self.textContainer {
? ? ? // 對textContainer多次操作
}
當我們命名一個可選值變量和屬性時,避免使用如optionalString 或 maybeView名稱,因為可選值的已經(jīng)體現(xiàn)在類型定義中。
在可選值綁定時,直接映射原始的命名比使用諸如unwrappedView 或 actualLabel好。
正例
var subview: UIView?
var volume: Double?
if let subview=subview, volume=volume {
? ? ? // xxoo
}
反例
var optionalSubview: UIView?
var volume: Double?
if let unwrappedSubview = optionalSubview {
? ? if let realVolume = volume {
? ? ? ? ? // ooxx
? ? }
}
結構體構造器
使用Swift原生的結構體構造器而不是傳統(tǒng)的的CGGeometry構造器。
正例
let bounds = CGRect(x: 40, y: 20, width: 120, height: 80)
let centerPoint = CGPoint(x: 96, y: 42)
反例
let bounds = CGRectMake(40,20,120,80)
let centerPoint = CGPointMake(96,42)
推薦使用結構體內(nèi)部的常量CGRect.infiniteRect,CGRect.nullRect等,來替代全局常量CGRectInfinite,CGRectNull等。對于已經(jīng)存在的變量值,可以簡寫成 .zero。
延遲初始化
延遲初始化用來細致地控制對象的生命周期,這對于想實現(xiàn)延遲加載視圖的UIViewController特別有用,你可以使用即時被調(diào)用閉包或私有構造方法:
lazy var locationManager: CLLocationManager=self.makeLocationManager()
private func makeLocationManager() -> CLLocationManager {
? ? let manager=CLLocationManager() ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?manager.desiredAccuracy=kCLLocationAccuracyBest?
? ? manager.delegate=self
? ? manager.requestAlwaysAuthorization()
? ? return manager
}
注意:Location manager 的負面效果會彈出對話框要求用戶提供權限,這是做延時加載的原因。
類型推斷
推薦使用更加緊湊的代碼,讓編譯器能夠推斷出每個常量和變量的類型。類型推斷也適用于小的數(shù)組和字典,如果需要可以指定特定的類型,如 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]()
類型注解對空的數(shù)組和字典
對空的數(shù)據(jù)和字典,使用類型注解。
正例
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>
函數(shù) VS 方法
自由函數(shù)不依附于任何類或類型,應該節(jié)制地使用。如果可能優(yōu)先使用方法而不是自由函數(shù),這有助于代碼的可讀性和易發(fā)現(xiàn)性。
自由函數(shù)使用的場景是當不確定它和具體的類別或實例相關。
正例
let sorted = items.mergeSort() // ?易發(fā)現(xiàn)性
rocket.launch() ?// ?可讀性
反例
let sorted = mergeSort(items)// ?不易被發(fā)現(xiàn)
launch(&rocket)
自由函數(shù)
let tuples = zip(a, b)?
let value = max(x,y,z)// ?天然自由函數(shù)!
內(nèi)存管理
代碼應避免指針循環(huán)引用,分析對象圖譜,使用弱引用?和未知引用避免強引用循環(huán). 另外使用值類型(結構體和枚舉)可以避免循環(huán)引用。
延長對象生命周期
延長對象生命周期習慣上使用[weak self] 和 guard let strongSelf = self else { return }.?[weak self]? 優(yōu)于 [unowned self] 因為前者更更能明顯地self 生命周期長于閉包塊。 顯式延長?生命周期優(yōu)先于?可選性拆包.
正例
resource.request().onComplete { [weak self] response in
? guard let strongSelf = self else {return}
? let model = strongSelf.updateModel(response)?
? strongSelf.updateUI(model)
}
反例
// 如果self在response之前銷毀會?崩潰
resource.request().onComplete { [unowned self] ?response in
? ? let model = self.updateModel(response)
? ? self.updateUI(model)
}
反例
//?self可能在updateModel和updateUI被釋放
resource.request().onComplete { [weak self] response in
? let model = self?.updateModel(response)
? self?.updateUI(model)
}
訪問控制
合理的使用private 和? fileprivate, 推薦使用private,在使用extension時可使用fileprivate。
訪問控制符一般放在屬性修飾符的最前面. 除非需要使用 static 修飾符 ,@IBAction,? @IBOutlet 或 @discardableResult 。
正例
class TimeMachine {
? ?private dynamic lazy var fluxCapacitor = FluxCapacitor()
}private let message = "Great Scott!"
反例
class TimeMachine {
? ?lazy dynamic private var fluxCapacitor=FluxCapacitor()
}fileprivate let message = "Great Scott!"
控制流程
循環(huán)使用for-in表達式,而不使用 while 表達式。
正例
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(from: 0, to: items.count, by: 2) {
? print(index)
}
for index in (0...3).reverse() {
? ? print(index)
}
反例
var i=0
while i<3 {
? print("Hello three times")
? i+=1
}
var i=0
while i<3 {
? let person = attendeeList[i]
? print("\(person)is at position #\(i)")?
? i+=1
}
黃金路徑
當編碼遇到條件判斷時,左邊的距離是黃金路徑或幸福路徑,因為路徑越短,速度越快。不要嵌套if循環(huán),多個返回語句是可以的。guard 就為此而生的。
正例
func computeFFT(context: Context?, inputData: InputData?) ?throws -> Frequencies {
? ? guard let context = context else {throwFFTError.NoContext }? ?guard let inputData = inputData else { throwFFTError.NoInputData }
? //?計算frequencies
? return frequencies
}
反例
func computeFFT(context: Context?, inputData: InputData?) throws -> Frequencies {
if let context = context {
? ? if let inputData = inputData {
? ? ? ? ? ?// 計算frequencies
? ? ? ? ? return frequencies? ?
? ? } else {
? ? ? ? ?throwFFTError.NoInputData? ?
? ? ?}?
? ?} else {
? ? ? throwFFTError.NoContext?
? ?}
}
當有多個條件需要用 guard 或 if let 解包,可用復合語句避免嵌套。
正例
guard let number1=number1, number2=number2, number3=number3 else{
? ? fatalError("impossible")
}
// 處理number
反例
if let number1=number1 {
? ? ?if let number2=number2 {
? ? ? ? ? ?if let number3=number3 {
? ? ? ? ? ? ? ?// 處理number
? ? ? ? ? ? ?} else {? ? ?
? ? ? ? ? ? ? fatalError("impossible")? ?
? ? ? ? ? ? ?}?
? ? ? ? ?} else {? ?
? ? ? ? ? ? fatalError("impossible")?
? ? ? ? ? }
? ? ? } else {
? ?fatalError("impossible")
}
?失敗防護
防護語句的退出有很多方式,一般都是單行語句,如 return,throw,break,continue 和 fatalError()等。 避免出現(xiàn)大的代碼塊,?如果清理代碼需要多個退出點,可以用 defer 模塊避免重復清理代碼。
分號
Swift不強制每條語句有分號,當一行內(nèi)寫多個語句的時候是必須的。
不要在一行里寫多個語句。
例外的是在構造for遞增循環(huán)時需要使用分號,但構造for循環(huán)時推薦使用 用 for-in ?結構。
正例
let swift = "not a scripting language"
反例
let swift = "not a scripting language";
圓括號
條件判斷時圓括號不是必須的,建議省略。
正例
if name=="Hello" {
? ? print("World")
}
反例
if(name=="Hello") {
? ?print("World")
}
推薦閱讀
更多
獲取更多內(nèi)容請關注微信公眾號豆志昂揚:
+ 直接添加公眾號豆志昂揚;
+ 微信掃描下圖二維碼;