官方 Swift 風(fēng)格指南?
一定要閱讀?Apple 的 API 設(shè)計規(guī)范。
具體的規(guī)范細節(jié)和附加說明如下。
本指南已于 2018 年 2 月 14 日針對 Swift 4.0 進行了更新。
1.1?使用 4 個空格代替 1 個?tabs?。
1.2?單行過長會引起閱讀不適,每行代碼盡量限制在 160 字符內(nèi) ( Xcode -> Preferences -> Text Editing -> Page guide at column 設(shè)置為160 將會很有幫助)
1.3?確保每個文件末尾都有一個新行。
1.4?確保任何地方都沒有尾隨的空格( Xcode -> Preferences -> Text Editing -> Automatically trim trailing whitespace 加上 Including whitespace-only lines )。
1.5?不要把左大括號放在新行 — 我們使用?1TBS 風(fēng)格。
1.6?當(dāng)為屬性、常量、變量、字典的鍵、函數(shù)參數(shù)、協(xié)議實現(xiàn)或父類書寫類型時,不要在冒號?:?前面加上空格。
1.7?通常,?,?逗號后面應(yīng)該有一個空格。
1.8?二元運算符前后都應(yīng)該有一個空格,比如?+?、?==?或?->。當(dāng)然,?(?后面和?)?前面就不要有空格了。
1.9?我們遵循 Xcode 推薦的縮進風(fēng)格(即按住 CTRL-I 時,代碼不再發(fā)生變化)。當(dāng)聲明的函數(shù)跨越多行時,推薦使用 Xcode 7.3 默認的語法風(fēng)格。
1.10?當(dāng)調(diào)用一個多參數(shù)函數(shù)時,將每個參數(shù)放置有額外縮進的單獨行中。
1.11?處理大到足以分成多行的隱式數(shù)組或字典時,按照方法、if?語句等語法中大括號的風(fēng)格使用?[?和?]?。方法中的閉包也應(yīng)該用類似的風(fēng)格處理。
1.12?盡可能避免多行語句,推薦使用局部常量或其他方法。
2.1?在 Swift 中不需要 Objective-C 風(fēng)格的前綴(比如用?GuybrushThreepwood?代替?LIGuybrushThreepwood)。
2.2?使用?PascalCase?為類型命名(比如?struct?、?enum?、?class?、?typedef?、?associatedtype?等等)。
2.3?對于函數(shù)、方法、屬性、常量、變量、參數(shù)名稱、枚舉選項,使用?camelCase?(首字母小寫)。
2.4?實際上,當(dāng)處理通常全部大寫的縮寫或其他名稱時,代碼里用到的任何名稱都使用大寫。例外情況是,如果這個單詞位于以需要用小寫開頭的名稱的開頭——在這種情況下,請使用全部小寫字母作為首字母縮寫詞。
2.5?所有與實例無關(guān)的常量都應(yīng)該用?static?修飾。所有這些?static?常量都應(yīng)該放置在他們的?class?、?struct或?enum?標(biāo)記過的部分中。 對于有很多常量的類,你應(yīng)該將擁有類似或相同前綴、后綴 和 / 或者使用情況的常量進行分組。
2.6?對于泛型和關(guān)聯(lián)類型,使用?PascalCase?描述泛型。如果這個單詞和它遵循的協(xié)議或者它繼承的父類沖突,你可以在關(guān)聯(lián)類型或泛型名稱后面追加?Type?后綴。
2.7?名稱應(yīng)具有描述性的和明確性。
2.8?不要縮寫、使用縮寫名稱或單字母名稱
2.9?如果不明顯,請在常量或變量名稱中包含類型信息。
2.10?命名函數(shù)參數(shù)時,請確保函數(shù)可以被輕易地閱讀并理解每個參數(shù)的目的。
2.11?按照?Apple 的 API 設(shè)計規(guī)范,如果protocol?描述「某事物在做什么」,那么應(yīng)被命名為名詞(比如?Collection?)。 如果?protocol?描述「一種能力」,使用后綴?able?、?ible?或?ing(比如?Equatable、ProgressReporting?)。如果兩種選項都不適用你的用例,你也可以在協(xié)議名稱后加一個?Protocol?后綴。一些?protocol?的例子如下。
3.1.1?盡可能選擇?let?而非?var?.
3.1.2?當(dāng)從一個集合轉(zhuǎn)換到另一個集合時,建議首選?map?,?filter?,?reduce?等高階函數(shù)。在使用這些方法時,請確保使用的閉包沒有任何副作用。
3.1.3?如果常量或變量的類型可以被推導(dǎo),則不去主動聲明它的類型。
3.1.4?如果一個方法返回多個值,那么推薦使用?inout?修飾的元組類型作為返回值類型 (如果類型不夠一目了然,最好使用命名元組來表明你要返回的內(nèi)容) 。 如果你會多次使用到某個特定的元組,那么可以考慮使用?typealias。 如果你的元組里返回了 3 個及以上的元素,那么使用?struct?或者?class?可能比元組更合適。
3.1.5?在為類聲明代理或者協(xié)議的時候,要注意循環(huán)引用,通常這些屬性在聲明時要用?weak?修飾。
3.1.6?在逃逸閉包中直接調(diào)用 self 的時候,要注意是否會引起循環(huán)引用。 - 當(dāng)可能發(fā)生循環(huán)引用時嘗試使用?capture list?:
3.1.7?不要使用labeled breaks.
3.1.8?流程控制語句的條件語句不需要加括弧。
3.1.9?可以使用點語法直接寫出枚舉值,前面不需要寫出枚舉類型
3.1.10?在聲明類方法的時候不要使用縮寫,因為和?enum?相比,推導(dǎo)類的上下文會更難。?
3.1.11?除非必要,否則盡量不使用?self.?。
3.1.12?寫方法時,要考慮這個方法是否會被重載。如果不會,標(biāo)記為?final,但請記住,這是為了防止以測試為目的而重載方法。通常,?final?方法會將編譯時間縮短,所以適時使用它是非常棒的。 但是,在庫中應(yīng)用?final?關(guān)鍵詞要非常小心。因為相對于在本地項目中將某些內(nèi)容改為非?final?,在庫中將某些內(nèi)容改為非?final?可不是小事。
3.1.13?使用諸如?else?、?catch?等后面跟隨代碼塊的語句,將關(guān)鍵字 和代碼塊放在同一行。強調(diào)一下,我們遵循?1TBS 風(fēng)格?。if?/?else?和?do?/?catch?的示例如下。
3.1.14?在定義與類相關(guān)的函數(shù)或?qū)傩远皇嵌x類的實例變量時時,推薦?static?,而不是?class。如果你特別需要在子類中重載這個函數(shù)的功能時,請使用?class?。但是,你應(yīng)該考慮使用?protocol?來達到這個目的。
3.1.15?如果有一個函數(shù)是無參數(shù)的、無副作用的而且返回某個對象或值,更推薦使用計算屬性來代替它。
3.2.1?如果需要寫訪問修飾符關(guān)鍵字的話,請將它寫在開頭。
3.2.2?訪問修飾符關(guān)鍵字不應(yīng)該獨占一行,而是將它和其描述的東西放在一行。
3.2.3?通常情況下,訪問修飾符關(guān)鍵字默認是?internal?,所以不用寫出來。
3.2.4?如果屬性需要被單元測試訪問,則需要將它標(biāo)記為?internal?,以便于使用?@testable import ModuleName。如果屬性?應(yīng)該?是私有的,但是出于單元測試的目的將它聲明為?internal,一定要添加適當(dāng)?shù)奈臋n注釋來解釋這一點。 為了更加簡明,你可以使用?- warning:?標(biāo)記語法,如下所示。
3.2.5?盡可能使用?private?而不是?fileprivate?。
3.2.6?當(dāng)在?public?和?open?兩者之間選擇一個時,如果你打算讓某些內(nèi)容在模塊外也可以被繼承,推薦使用?open?,否則請使用?public。注意,任何?internal?或更高訪問權(quán)限的內(nèi)容,都可以通過使用?@testable import?在測試中被繼承。所以這不應(yīng)該成為使用?open?的理由。通常,在涉及到庫的時候,更傾向于自由地使用?open?。但是,?open?可以輕易地同時改變應(yīng)用程序中多個模塊的內(nèi)容。當(dāng)涉及到這類代碼庫中的模塊時,更傾向于保守地使用?open?。
推薦創(chuàng)建自定義運算符。
如果要引入自定義運算符,確保你有一個很好的理由,為什么你想把一個新的運算符引入全局范圍,而不是使用其他現(xiàn)有的運算符。
可以重寫現(xiàn)有的運算符以支持新類型(特別是?==?)。然而,你新定義的必須保存運算符的語義。例如,?==?必須是檢測是否相等并返回檢測結(jié)果的布爾值。
3.4.1?當(dāng)使用具有有限可能性的 switch 語句(?enum?),不包括?default?的其他情況。將未處理的情況放置在?default?里,并使用?break?來結(jié)束執(zhí)行。
3.4.2?在 Swift 中由于?switch?的各種情況中默認有?break?,如果不需要,可以省略?break?關(guān)鍵字。?
3.4.3?case?和?switch?的聲明要按照 Swift 的規(guī)范獨占一行。
3.4.4?當(dāng)定義具有關(guān)聯(lián)值的情況時,確保這個值被適當(dāng)?shù)臉?biāo)記,例如:case hunger(hungerLevel: Int)?而不是?case hunger(Int)?。
3.4.5?更推薦使用?fallthrough?關(guān)鍵字來處理一系列的 cases (例如:??case 1, 2, 3:?)。
3.4.6?如果您有一個不應(yīng)該達到的默認情況,最好拋出一個錯誤(或處理其他類似的方法,如斷言)。
3.5.1?使用隱式解包可選類型的唯一機會是使用?@IBOutlet?的時候。在其他情況下,使用非可選或常規(guī)可選的屬性會更好。是的,有某些情況下,你可以「保證」使用時屬性不會為?nil?,但是安全和一致會更好。同樣,不要使用強制解包。
3.5.2?不要使用?as!?或?try!.
3.5.3?如果你不打算真正地使用存在可選類型中的值,但需要判斷這個值是否為?nil?,顯式地檢查這個值是不是?nil?,而不是使用?if let?語法。
3.5.4?不要使用?unowned?。你可以將?unowned?視為被隱式解包的?weak?屬性的等價物(雖然?unowned?因為完全忽略引用計數(shù)而略有性能上的提升)。因為我們不想有隱式解包,所以我們同樣也不想要?unowned?屬性。
3.5.5?在解包可選類型時,在恰當(dāng)?shù)牡胤绞褂孟嗤Q來命名解包的常量或變量。
在實現(xiàn)協(xié)議時,有兩種方式組織代碼:
使用?// MARK:?注釋將協(xié)議實現(xiàn)和其他部分的代碼隔開。
在同一資源文件中?class/struct?實現(xiàn)代碼以外的地方,使用擴展。
記住使用擴展時,無論怎樣,擴展中的方法不要被子類重載,這會使測試變麻煩。如果這是一個通用的使用場景,為了一致性使用方法 #1 可能會更好。否則,#2 可以使關(guān)系的拆分更清楚。?
即使使用方法 #2 ,也要添加?// MARK:?語句,以便在 Xcode 的方法 / 屬性 / 類等的列表 UI 中更加易讀。
3.7.1?如果創(chuàng)建只讀的計算屬性,提供不帶??get {}?的獲取方法。
3.7.2?使用?get {}?、?set {}?、?willSet?和?didSet?時,縮進這些塊。
3.7.3?雖然你可以為?willSet/didSet?和?set?自定義新值或舊值的名稱,但請使用默認提供的標(biāo)準標(biāo)識符?newValue?/?oldValue?。
3.7.4?你可以按如下方式聲明一個單例屬性:
3.8.1?如果可以明確參數(shù)類型,即可以省略參數(shù)類型也可以顯示參數(shù)類型。你可以根據(jù)場景決定是否添加一些說明來提高代碼的可讀性,或者是省略一些無關(guān)緊要的部分。
3.8.2?聲明了一個閉包,不需要用括號括起來,除非需要(例如,閉包類型是可選的,或者這個閉包在另一個閉包內(nèi))。閉包的參數(shù)都是是放在圓括號里,如果用?()?就表示沒有參數(shù),用?Void?表示無返回值。
3.8.3?在閉包中盡可能的讓參數(shù)保持在同一行,避免過多換行。(確保每行小于160個字符)。
3.8.4?如果閉包的含義不太明確可以使用尾隨閉包(如果一個方法同時含有成功和失敗的兩個閉包就不建議使用尾隨閉包)。
3.9.1?通常要避免直接用下標(biāo)的方式訪問數(shù)組。盡可能使用訪問器,比如?.first?或?.last。它們是可選類型并且不會導(dǎo)致崩潰。推薦盡可能地使用?for item in items?語法而不是類似與?for i in 0 ..< items.count?的語法。如果你需要直接用下標(biāo)訪問數(shù)組,一定要做適當(dāng)?shù)倪吔鐧z查。你可以使用?for (index, value) in items.enumerated()?來一并得到索引和值。
3.9.2?不要使用?+=?或?+?操作符來追加或串聯(lián)到數(shù)組。而是使用?.append()?或?.append(contentsOf:),因為在 Swift 當(dāng)前的狀況下它們(至少在編譯方面)擁有更高的性能。如果基于其他數(shù)組聲明數(shù)組而且想讓它保持不變,使用?let myNewArray = [arr1, arr2].joined(),而不是?let myNewArray = arr1 + arr2。
假設(shè)函數(shù)?myFunction?應(yīng)該返回?String,但是,某些時候它會運行錯誤。在出錯時返回nil的情況下,通用的處理方式是讓函數(shù)返回可選類型?String?。
例如:
相反,在適當(dāng)?shù)臅r候,我們應(yīng)該使用 Swift 的?try/catch?操作來了解失敗原因。
你可以使用?struct,如下所示:
用法示例:
有一些例外情況,使用可選類型比使用錯誤處理更有意義。當(dāng)返回結(jié)果語義上可能是??nil,而不是取回結(jié)果時的錯誤值時,返回可選類型比使用錯誤處理更有意義。
通常,如果方法可能「失敗」,并且返回值為可選類型,失敗的原因就不是很明顯了,那么方法拋出錯誤可能會更有意義。
3.11.1?一般情況下,我們推薦在適用的地方使用「盡早返回」的策略 而不是在?if?語句里嵌套代碼。在這種使用場景下,使用?guard?語句通常很有用,而且可以提升代碼的可讀性。
3.11.2?當(dāng)解包可選類型時,推薦?guard?語句而不是?if?語句來減少代碼中嵌套縮進的數(shù)量。
3.11.3?在解包類型不復(fù)雜,需要在使用?if?還是?guard?之間做抉擇時,要記住最重要的是代碼的可讀性。會有很多可能的情況,例如依賴于兩個不同的布爾值、復(fù)雜邏輯語句涉及到多個判斷等,所以通常使用您的最佳的判斷來寫出可讀且一致的代碼。如果你不確定?guard?或?if?哪個更具可讀性或者它們看起來同樣可讀,推薦使用?guard。
3.11.4?如果在兩種語句之間做選擇,使用?if?語句比使用?guard?語句更有意義。
3.11.5?只有在失敗會導(dǎo)致退出當(dāng)前上下文的情況下,才應(yīng)該使用?guard。 下面是一個例子,在其中使用兩個?if?語句而不是使用兩個?guard?語句更有意義—有兩個不相互阻塞的無關(guān)條件。
3.11.6?通常,我們可能遇到需要使用?guard?語句解包多個可選類型的情況。一般情況下,如果處理每個解包的失敗是相同的(例如,return、break、continue、throw?或一些其他的?@noescape),將解包合入一個?guard語句。
3.11.7?不要將?guard?語句寫成只有一行。
如果函數(shù)比簡單的 O(1) 操作負責(zé),通常應(yīng)該考慮為函數(shù)加個文檔。因為方法簽名的一些信息可能不是那么明顯。如果實現(xiàn)方式有任何怪癖,無論在技術(shù)上有趣、棘手、不明顯等等,都應(yīng)該被文檔化。應(yīng)該為復(fù)雜的類 / 結(jié)構(gòu)體 / 枚舉 / 協(xié)議和屬性添加文檔。所有的?public?函數(shù) / 類 / 屬性 / 常量 / 結(jié)構(gòu)體 / 枚舉 / 協(xié)議等也應(yīng)該被文檔化。(如果,他們的簽名 / 名稱不能使他們含義 / 功能很明顯)。
寫完文檔注釋之后,你應(yīng)該按住 option 鍵并單擊函數(shù) / 屬性 / 類等等來確認文檔注釋被正確地格式化了。
務(wù)必查看 Swift 注釋標(biāo)記中提供的全套功能,詳見?Apple 的文檔。
原則:
4.1.1?160個字符列的限制(和代碼的部分一樣)。
4.1.2?如果文檔注釋在一行內(nèi),使用(?/** */?)。
4.1.3?不要在每一個附加行前面加?*。
4.1.4?使用新的?- parameter?語法而不是舊的?:param:?語法(務(wù)必使用小寫的?parameter?而并不是?Parameter)。 按住 Option 鍵并單擊你寫的方法以確??焖賻椭雌饋硎钦_的。
4.1.5?如果你要給方法的參數(shù) / 返回值 / 拋出的異常寫文檔,即使某些文檔最終會有重復(fù),也請將它們都寫入文檔(這比文檔看起來不完整更可取)。有時,如果僅有一個參數(shù)需要寫文檔,在描述中提及它更好一些。
4.1.6?對于復(fù)雜的類,請使用一些看起來合適的示例來描述類的用法。記住在 Swift 注釋文檔中可以使用 markdown 語法。因此,換行符、列表等等都是適用的。
4.1.7?提及代碼時,請使用代碼提示 - `
4.1.8?寫文檔注釋時,盡可能保持簡潔。
4.2.1?始終在?//?后面加個空格。
4.2.2?始終在自己的行中寫注釋。
4.2.3?使用?// MARK: - 無論是什么?時,在注釋后加個空行。