前言
注:本文檔借鑒與多個(gè)文檔整理而成伪嫁,如觀看時(shí)引起你的不適,我深表抱歉膛腐!
關(guān)于Swift的代碼的相關(guān)規(guī)范,不同的開(kāi)發(fā)者都有自己相應(yīng)的規(guī)范鼎俘,可能還是很多人根本就沒(méi)有規(guī)范哲身。為了保證同一個(gè)公司同一個(gè)項(xiàng)目組中代碼美觀并且一致,這里寫(xiě)下這份我們公司的Swift編程規(guī)范指南贸伐。該指南首要目標(biāo)是讓代碼緊湊律罢,可讀性更高且簡(jiǎn)潔。當(dāng)然不一定適用于所有公司棍丐。
APP版本號(hào)規(guī)范
以三位數(shù)版本號(hào)控制 --> 0.0.1
Bug修復(fù)遞增最后一位 --> 0.0.2
新增功能遞增第二位重置第三位為0 --> 0.1.0
bug修復(fù)及新增遞增第二位,重置第三位為0 --> 0.2.0
重大更新及bug修復(fù)遞增第一位误辑,第二位,第三位 重置為0 --> 1.0.0
版本號(hào)遞增規(guī)則上不封頂(以當(dāng)前使用的 Mac 系統(tǒng)版本為例) -->10.12.6
代碼規(guī)范
- 每行字符限制長(zhǎng)度,避免一行過(guò)長(zhǎng)影響閱讀( 160行)
Xcode->Preferences->Text Editing->Page guide at column
- 要在逗號(hào)后面加空格
let array = [1, 2, 3, 4, 5];
- 二元運(yùn)算符(+歌逢,==巾钉,或>)的前后都需要添加空格,左小括號(hào)和右小括號(hào)前面不需要空格秘案。
let value = 20 + (34 / 2) * 3
if 1 + 1 == 2 {
//TODO
}
func pancake -> Pancake {
/** do something **/
}
- 縮進(jìn)有序
class SomeClass {
func someMethod() {
if x == y {
/* ... */
} else if x == z {
/* ... */
} else {
/* ... */
}
}
/* ... */
}
- 遵守Xcode內(nèi)置的縮進(jìn)格式砰苍,當(dāng)聲明的一個(gè)函數(shù)需要跨多行時(shí)潦匈,推薦使用Xcode默認(rèn)格式。
// Xcode針對(duì)跨多行函數(shù)聲明縮進(jìn)
func myFunctionWithManyParameters(parameterOne: String,
parameterTwo: String,
parameterThree: String) {
// Xcode會(huì)自動(dòng)縮進(jìn)
print("\(parameterOne) \(parameterTwo) \(parameterThree)")
}
// Xcode針對(duì)多行 if 語(yǔ)句的縮進(jìn)
if myFirstVariable > (mySecondVariable + myThirdVariable)
&& myFourthVariable == .SomeEnumValue {
// Xcode會(huì)自動(dòng)縮進(jìn)
print("Hello, World!")
}
- 當(dāng)調(diào)用一個(gè)函數(shù)有多個(gè)參數(shù)時(shí)赚导,每個(gè)參數(shù)另起一行茬缩,比函數(shù)名多一個(gè)縮進(jìn)。
functionWithArguments(
firstArgument: "Hello, I am a string",
secondArgument: resultFromSomeFunction()
thirdArgument: someOtherLocalVariable)
- 當(dāng)遇到需要處理的數(shù)組或字典內(nèi)容較多需要多行顯示時(shí)吼旧,需要把[和]類(lèi)似方法體里面的括號(hào)凰锡,方法體里的閉合也要做類(lèi)似的處理。
functionWithBunchOfArguments(
someStringArgument: "hello I am a string",
someArrayArgument: [
"dadada daaaa daaaa dadada daaaa daaaa dadada daaaa daaaa",
"string one is crazy - what is it thinking?"
],
someDictionaryArgument: [
"dictionary key 1": "some value 1, but also some more text here",
"dictionary key 2": "some value 2"
],
someClosure: { parameter1 in
print(parameter1)
})
- 優(yōu)雅的使用多重邏輯判斷
// 推薦
let firstCondition = x == firstReallyReallyLongPredicateFunction()
let secondCondition = y == secondReallyReallyLongPredicateFunction()
let thirdCondition = z == thirdReallyReallyLongPredicateFunction()
if firstCondition && secondCondition && thirdCondition {
// 你要干什么
}
// 不推薦
if x == firstReallyReallyLongPredicateFunction()
&& y == secondReallyReallyLongPredicateFunction()
&& z == thirdReallyReallyLongPredicateFunction() {
// 你要干什么
}
命名
- 使用帕斯卡拼寫(xiě)法(又名大駱駝拼寫(xiě)法圈暗,首字母大寫(xiě))為類(lèi)型命名(如struct掂为,enum,class员串,typedef勇哗,associatedtype等)。
- 使用小駱駝拼寫(xiě)法(首字母小寫(xiě))為函數(shù)寸齐,方法欲诺,常亮,參數(shù)等命名渺鹦。
- 首字母縮略詞在命名中一般來(lái)說(shuō)都是全部大寫(xiě)瞧栗,例外的情形是如果首字母縮略詞是一個(gè)命名的開(kāi)始部分,而這個(gè)命名需要小寫(xiě)字母作為開(kāi)頭海铆,這種情形下首字母縮略詞全部小寫(xiě)迹恐。
// "HTML" 是變量名的開(kāi)頭, 需要全部小寫(xiě) "html"
let htmlBodyContent: String = "<p>Hello, World!</p>"
// 推薦使用 ID 而不是 Id
let profileID: Int = 1
// 推薦使用 URLFinder 而不是 UrlFinder
class URLFinder {
/* ... */
}
- 使用個(gè)人名字前綴 k + 大駱駝命名法 為所有非單例的靜態(tài)常量命名。
class ClassName {
// 基元常量使用 k 作為前綴
static let kSomeConstantHeight: CGFloat = 80.0
// 非基元常量也是用 k 作為前綴
static let kDeleteButtonColor = UIColor.redColor()
// 對(duì)于單例不要使用k作為前綴
static let sharedInstance = MyClassName()
/* ... */
}
- 命名應(yīng)該具有描述性個(gè)清晰性的卧斟。
// 推薦
class RoundAnimatingButton: UIButton { /* ... */ }
// 不推薦
class CustomButton: UIButton { /* ... */ }
- 一般不要縮寫(xiě)殴边、簡(jiǎn)寫(xiě)或單個(gè)字母命名。
// 推薦
class RoundAnimatingButton: UIButton {
let animationDuration: NSTimeInterval
func startAnimating() {
let firstSubview = subviews.first
}
}
// 不推薦
class RoundAnimating: UIButton {
let aniDur: NSTimeInterval
func srtAnmating() {
let v = subviews.first
}
}
- 如果原有命名不能明顯表明類(lèi)型珍语,則屬性命名內(nèi)要包括類(lèi)型信息锤岸。
// 推薦
class ConnectionTableViewCell: UITableViewCell {
let personImageView: UIImageView
let animationDuration: NSTimeInterval
// 作為屬性名的firstName,很明顯是字符串類(lèi)型板乙,所以不用在命名里不用包含String
let firstName: String
// 雖然不推薦, 這里用 Controller 代替 ViewController 也可以是偷。
let popupController: UIViewController
let popupViewController: UIViewController
// 如果需要使用UIViewController的子類(lèi),如TableViewController, CollectionViewController, SplitViewController, 等募逞,需要在命名里標(biāo)名類(lèi)型蛋铆。
let popupTableViewController: UITableViewController
// 當(dāng)使用outlets時(shí), 確保命名中標(biāo)注類(lèi)型。
@IBOutlet weak var submitButton: UIButton!
@IBOutlet weak var emailTextField: UITextField!
// 沒(méi)有注釋的前面空出一行
@IBOutlet weak var nameLabel: UILabel!
// 多個(gè)組合單詞建議使用"_"進(jìn)行區(qū)分
@IBOutlet weak var create_timeLabel: UILabel!
// 不需要外部使用的屬性記得加上private修飾
private let edgeMargin : CGFloat = 62
}
// 不推薦
class ConnectionTableViewCell: UITableViewCell {
// 這個(gè)不是 UIImage, 不應(yīng)該以Image 為結(jié)尾命名放接。
// 建議使用 personImageView
let personImage: UIImageView
// 這個(gè)不是String刺啦,應(yīng)該命名為 textLabel
let text: UILabel
// animation 不能清晰表達(dá)出時(shí)間間隔
// 建議使用 animationDuration 或 animationTimeInterval
let animation: NSTimeInterval
// transition 不能清晰表達(dá)出是String
// 建議使用 transitionText 或 transitionString
let transition: String
// 這個(gè)是ViewController,不是View
let popupView: UIViewController
// 由于不建議使用縮寫(xiě)纠脾,這里建議使用 ViewController替換 VC
let popupVC: UIViewController
// 技術(shù)上講這個(gè)變量是 UIViewController, 但應(yīng)該表達(dá)出這個(gè)變量是TableViewController
let popupViewController: UITableViewController
// 為了保持一致性玛瘸,建議把類(lèi)型放到變量的結(jié)尾蜕青,而不是開(kāi)始,如submitButton
@IBOutlet weak var btnSubmit: UIButton!
@IBOutlet weak var buttonSubmit: UIButton!
// 在使用outlets 時(shí)糊渊,變量名內(nèi)應(yīng)包含類(lèi)型名右核。
// 這里建議使用 firstNameLabel
@IBOutlet weak var firstName: UILabel!
}
當(dāng)給函數(shù)參數(shù)命名時(shí),要確保函數(shù)能夠理解每個(gè)參數(shù)的目的渺绒。
代碼風(fēng)格
綜合
- 盡可能多使用let贺喝,少使用var。
- 當(dāng)需要遍歷一個(gè)集合變形成另一個(gè)集合時(shí)芒篷,推薦使用函數(shù)flatMap搜变,filter采缚,和reduce针炉。
// 推薦
let stringOfInts = [1, 2, 3].flatMap { String($0) }
// ["1", "2", "3"]
// 不推薦
var stringOfInts: [String] = []
for integer in [1, 2, 3] {
stringOfInts.append(String(integer))
}
// 推薦
let evenNumbers = [4, 8, 15, 16, 23, 42].filter { $0 % 2 == 0 }
// [4, 8, 16, 42]
// 不推薦
var evenNumbers: [Int] = []
for integer in [4, 8, 15, 16, 23, 42] {
if integer % 2 == 0 {
evenNumbers(integer)
}
}
- 如果變量類(lèi)型可以依靠判斷得出,不建議聲明變量時(shí)指明類(lèi)型扳抽。
- 如果一個(gè)函數(shù)有多個(gè)返回值篡帕,推薦使用 元組 而不是inout參數(shù),如果這個(gè)元組在多個(gè)地方都會(huì)使用贸呢,建議使* 用typealias來(lái)定義這個(gè)元組镰烧,而如果返回的元組有三個(gè)或者三個(gè)以上的元素,建議使用結(jié)構(gòu)體或類(lèi)楞陷。
func pirateName() -> (firstName: String, lastName: String) {
return ("Guybrush", "Threepwood")
}
let name = pirateName()
let firstName = name.firstName
let lastName = name.lastName
- 當(dāng)使用委托和協(xié)議時(shí)怔鳖,請(qǐng)注意避免出現(xiàn)循環(huán)引用,基本上是在定義屬性的時(shí)候使用weak修飾固蛾。
- 在閉包里使用self的時(shí)候需要注意避免出現(xiàn)循環(huán)引用结执,使用捕獲列表可以避免這一點(diǎn)。
functionWithClosure() { [weak self] (error) -> Void in
// 方案 1
self?.doSomething()
// 或方案 2
guard let strongSelf = self else {
return
}
strongSelf.doSomething()
}
- switch 模塊中不用顯式使用break艾凯。
- 斷言流程控制的時(shí)候不要使用小括號(hào)献幔。
// 推薦
if x == y {
/* ... */
}
// 不推薦
if (x == y) {
/* ... */
}
- 在寫(xiě)枚舉類(lèi)型的時(shí)候,盡量簡(jiǎn)寫(xiě)趾诗。
// 推薦
imageView.setImageWithURL(url, type: .person)
// 不推薦
imageView.setImageWithURL(url, type: AsyncImageView.Type.person)
- 在使用類(lèi)方法的時(shí)候不用簡(jiǎn)寫(xiě)蜡感,因?yàn)轭?lèi)方法和枚舉類(lèi)型不一樣,不能輕易地推導(dǎo)出上下文恃泪。
// 推薦
imageView.backgroundColor = UIColor.whiteColor()
// 不推薦
imageView.backgroundColor = .whiteColor()
- 不建議使用self郑兴,除非必須得要。
- 在寫(xiě)一個(gè)方法的時(shí)候贝乎,需要衡量這個(gè)方法將來(lái)是否會(huì)被重寫(xiě)杈笔,如果不是請(qǐng)用final關(guān)鍵字修飾,這樣組織方法被重寫(xiě)糕非。一般來(lái)說(shuō)final修飾符可以優(yōu)化編譯速度蒙具,在合適的時(shí)候大膽的使用它吧球榆。需要注意的是,在一個(gè)公開(kāi)發(fā)布的代碼庫(kù)中使用final和在本地項(xiàng)目中使用final的影響差別很大禁筏。
- 在使用一些語(yǔ)句如else持钉,catch等緊隨代碼塊關(guān)鍵字的時(shí)候,確保代碼塊和關(guān)鍵字在同一行篱昔。
if someBoolean {
// 你想要什么
} else {
// 你不想做什么
}
do {
let fileContents = try readFile("filename.txt")
} catch {
print(error)
}
訪問(wèn)控制修飾符
- 如果需要把訪問(wèn)修飾符放到第一個(gè)位置每强。
// 推薦
private static let kMyPrivateNumber: Int
// 不推薦
static private let kMyPrivateNumber: Int
- 訪問(wèn)修飾符不應(yīng)該單獨(dú)另起一行,應(yīng)和訪問(wèn)修飾符描述的對(duì)象保持在同一行州刽。
// 推薦
public class Pirate {
/* ... */
}
// 不推薦
public
class Pirate {
/* ... */
}
- 默認(rèn)的訪問(wèn)修飾符是internal空执,可省略不寫(xiě)。
- 當(dāng)一個(gè)變量需要被單元測(cè)試 訪問(wèn)時(shí)穗椅,需要聲明為 internal 類(lèi)型來(lái)使用@testable import {ModuleName}辨绊。 如果一個(gè)變量實(shí)際上是private 類(lèi)型,而因?yàn)閱卧獪y(cè)試需要被聲明為 internal 類(lèi)型匹表,確定添加合適的注釋文檔來(lái)解釋為什么這么做门坷。這里添加注釋推薦使用 - warning: 標(biāo)記語(yǔ)法。
/**
這個(gè)變量是private 名字
- warning: 定義為 internal 而不是 private 為了 `@testable`.
*/
let pirateName = "LeChuck"
自定義操作符
- 不推薦使用自定義操作符袍镀,如果需要?jiǎng)?chuàng)建函數(shù)代替默蚌。
- 在重寫(xiě)操作符之前,請(qǐng)慎重考慮是否有充分的理由一定要在全局范圍內(nèi)創(chuàng)建新的操作符苇羡,而不是使用其他策略绸吸。
- 你可以重載現(xiàn)有的操作符來(lái)支持新的類(lèi)型(特別是==),但是新定義的必須保留操作符原來(lái)的含義设江,比如==必須用來(lái)測(cè)試是否相等并返回布爾值锦茁。
switch語(yǔ)句和枚舉
- 使用swift語(yǔ)句時(shí),如果選項(xiàng)是有限集合時(shí)绣硝,不要使用default蜻势,相反的,把一些不用的選項(xiàng)放到底部鹉胖,并用break關(guān)鍵詞阻止其執(zhí)行握玛。
- 應(yīng)為swift中的switch選項(xiàng)默認(rèn)是包含break的,所以不需要使用break關(guān)鍵字甫菠。
- case 語(yǔ)句 應(yīng)和 switch 語(yǔ)句左對(duì)齊挠铲,并在 標(biāo)準(zhǔn)的 default 上面。
- 當(dāng)定義的選項(xiàng)有關(guān)聯(lián)值時(shí)寂诱,確保關(guān)聯(lián)值有恰當(dāng)?shù)拿Q(chēng)拂苹,而不只是類(lèi)型。
enum Problem {
case attitude
case hair
case hunger(hungerLevel: Int)
}
func handleProblem(problem: Problem) {
switch problem {
case .attitude:
print("At least I don't have a hair problem.")
case .hair:
print("Your barber didn't know when to stop.")
case .hunger(let hungerLevel):
print("The hunger level is \(hungerLevel).")
}
}
- 推薦盡可能使用fall through痰洒。
- 如果default 的選項(xiàng)不應(yīng)該觸發(fā)瓢棒,可以拋出錯(cuò)誤 或 斷言類(lèi)似的做法浴韭。
func handleDigit(digit: Int) throws {
case 0, 1, 2, 3, 4, 5, 6, 7, 8, 9:
print("Yes, \(digit) is a digit!")
default:
throw Error(message: "The given number was not a digit.")
}
可選類(lèi)型
- 唯一使用隱式拆包可選型的場(chǎng)景是結(jié)合@IBOutlets,在其他場(chǎng)景使用非可選類(lèi)型和常規(guī)可選類(lèi)型脯宿,即使有的場(chǎng)景你確定有的變量使用的時(shí)候永遠(yuǎn)不會(huì)為nil念颈,但這樣做可以保持一致性和程序更加健壯。
- 不要使用as!和try!连霉,除非萬(wàn)不得已榴芳。
- 如果對(duì)于一個(gè)變量你不打算聲明為可選類(lèi)型,但當(dāng)需要檢查變量值是否為nil跺撼,推薦使用當(dāng)前值和nil直接比較窟感,而不推薦使用if let的語(yǔ)法。并且nil在前變量在后歉井。
// 推薦
if nil != someOptional {
// 你要做什么
}
// 不推薦
if let _ = someOptional {
// 你要做什么
}
- 不要使用unowned柿祈,unowned和weak修飾變量基本上等價(jià),并且都是隱式拆包(unowned在引用計(jì)數(shù)上有少許性能優(yōu)化)酣难,由于不推薦使用隱式拆包谍夭,也不推薦使用unowned變量黑滴。
// 推薦
weak var parentViewController: UIViewController?
// 不推薦
weak var parentViewController: UIViewController!
unowned var parentViewController: UIViewController
guard let myVariable = myVariable else {
return
}
協(xié)議
在實(shí)現(xiàn)協(xié)議的時(shí)候憨募,有兩種方式來(lái)組織你的代碼:
- 使用//MAKR:注釋來(lái)實(shí)現(xiàn)分割協(xié)議和其他代碼。
- 使用 extension 在 類(lèi)/結(jié)構(gòu)體已有代碼外袁辈,但在同一個(gè)文件內(nèi)菜谣。
- 請(qǐng)注意 extension 內(nèi)的代碼不能被子類(lèi)重寫(xiě),這也意味著測(cè)試很難進(jìn)行晚缩。 如果這是經(jīng)常發(fā)生的情況尾膊,為了代碼一致性最好統(tǒng)一使用第一種辦法。否則使用第二種辦法荞彼,其可以代碼分割更清晰冈敛。使用而第二種方法的時(shí)候,使用 // MARK: 依然可以讓代碼在 Xcode 可讀性更強(qiáng)鸣皂。
屬性 - 對(duì)于只讀屬性抓谴,提供getter而不是get{}。
var computedProperty: String {
if someBool {
return "I'm a mighty pirate!"
}
return "I'm selling these fine leather jackets."
}
- 對(duì)于屬性相關(guān)方法 get {}, set {}, willSet, 和 didSet, 確蹦欤縮進(jìn)相關(guān)代碼塊癌压。
- 對(duì)于willSet/didSet 和 set 中的舊值和新值雖然可以自定義名稱(chēng),但推薦使用默認(rèn)標(biāo)準(zhǔn)名稱(chēng) newValue/oldValue荆陆。
var computedProperty: String {
get {
if someBool {
return "I'm a mighty pirate!"
}
return "I'm selling these fine leather jackets."
}
set {
computedProperty = newValue
}
willSet {
print("will set to \(newValue)")
}
didSet {
print("did set from \(oldValue) to \(newValue)")
}
}
- 在創(chuàng)建常量的時(shí)候滩届,使用static關(guān)鍵字修飾。
class MyTableViewCell: UITableViewCell {
static let kReuseIdentifier = String(MyTableViewCell)
static let kCellHeight: CGFloat = 80.0
}
- 聲明單例屬性可以通過(guò)下面方式進(jìn)行:
class PirateManager {
static let sharedInstance = PirateManager()
/* ... */
}
閉包
如果參數(shù)的類(lèi)型很明顯被啼,可以在函數(shù)名里可以省略參數(shù)類(lèi)型, 但明確聲明類(lèi)型也是允許的帜消。 代碼的可讀性有時(shí)候是添加詳細(xì)的信息棠枉,而有時(shí)候部分重復(fù),根據(jù)你的判斷力做出選擇吧泡挺,但前后要保持一致性术健。
// 省略類(lèi)型
doSomethingWithClosure() { response in
print(response)
}
// 明確指出類(lèi)型
doSomethingWithClosure() { response: NSURLResponse in
print(response)
}
// map 語(yǔ)句使用簡(jiǎn)寫(xiě)
[1, 2, 3].flatMap { String($0) }
- 如果使用捕捉列表 或 有具體的非 Void返回類(lèi)型,參數(shù)列表應(yīng)該在小括號(hào)內(nèi)粘衬, 否則小括號(hào)可以省略荞估。
// 因?yàn)槭褂貌蹲搅斜恚±ㄌ?hào)不能省略稚新。
doSomethingWithClosure() { [weak self] (response: NSURLResponse) in
self?.handleResponse(response)
}
// 因?yàn)榉祷仡?lèi)型勘伺,小括號(hào)不能省略。
doSomethingWithClosure() { (response: NSURLResponse) -> String in
return String(response)
}
- 如果閉包是變量類(lèi)型褂删,不需把變量值放在括號(hào)中飞醉,除非需要,如變量類(lèi)型是可選類(lèi)型(Optional?)屯阀, 或當(dāng)前閉包在另一個(gè)閉包內(nèi)缅帘。確保閉包里的所以參數(shù)放在小括號(hào)中,這樣()表示沒(méi)有參數(shù)难衰,Void 表示不需要返回值钦无。
let completionBlock: (success: Bool) -> Void = {
print("Success? \(success)")
}
let completionBlock: () -> Void = {
print("Completed!")
}
let completionBlock: (() -> Void)? = nil
數(shù)組
- 基本上不要通過(guò)下標(biāo)直接訪問(wèn)數(shù)組內(nèi)容,如果可能使用.first或.last盖袭,因?yàn)檫@些方法是非強(qiáng)制類(lèi)型并不會(huì)奔潰失暂。推薦盡可能使用 for item in items 而不是 for i in 0..n。
- 不是使用+=或+操作符給數(shù)組添加新元素鳄虱,使用性能較好的.append()或appendContentsOf()弟塞,如果需要聲明數(shù)組基于其他數(shù)組并保持不可變類(lèi)型,使用let myNewArray = [arr1, arr2].flatten()拙已,而不是let myNewArray = arr1 + arr2 决记。
- 錯(cuò)誤處理:
假設(shè)一個(gè)函數(shù) myFunction 返回類(lèi)型聲明為 String,但是總有可能函數(shù)會(huì)遇到error倍踪,有一種解決方案是返回類(lèi)型聲明為 String?, 當(dāng)遇到錯(cuò)誤的時(shí)候返回 nil系宫。
func readFile(withFilename filename: String) -> String? {
guard let file = openFile(filename) else {
return nil
}
let fileContents = file.read()
file.close()
return fileContents
}
func printSomeFile() {
let filename = "somefile.txt"
guard let fileContents = readFile(filename) else {
print("不能打開(kāi) \(filename).")
return
}
print(fileContents)
}
- 實(shí)際上如果預(yù)知失敗的原因,我們應(yīng)該使用Swift 中的 try/catch 惭适。
定義 錯(cuò)誤對(duì)象 結(jié)構(gòu)體如下:
struct Error: ErrorType {
public let file: StaticString
public let function: StaticString
public let line: UInt
public let message: String
public init(message: String, file: StaticString = #file, function: StaticString = #function, line: UInt = #line) {
self.file = file
self.function = function
self.line = line
self.message = message
}
}
- 使用案例:
func readFile(withFilename filename: String) throws -> String {
guard let file = openFile(filename) else {
throw Error(message: “打不開(kāi)的文件名稱(chēng) \(filename).")
}
let fileContents = file.read()
file.close()
return fileContents
}
func printSomeFile() {
do {
let fileContents = try readFile(filename)
print(fileContents)
} catch {
print(error)
}
}
- 其實(shí)項(xiàng)目中還是有一些場(chǎng)景更適合聲明為可選類(lèi)型笙瑟,而不是錯(cuò)誤捕捉和處理,比如在獲取遠(yuǎn)端數(shù)據(jù)過(guò)程中遇到錯(cuò)誤癞志,nil作為返回結(jié)果是合理的往枷,也就是聲明返回可選類(lèi)型比錯(cuò)誤處理更合理。
整體上說(shuō),如果一個(gè)方法有可能失敗错洁,并且使用可選類(lèi)型作為返回類(lèi)型會(huì)導(dǎo)致錯(cuò)誤原因湮沒(méi)秉宿,不妨考慮拋出錯(cuò)誤而不是吃掉它。 - 使用 guard 語(yǔ)句
總體上屯碴,我們推薦使用提前返回的策略描睦,而不是 if 語(yǔ)句的嵌套。使用 guard 語(yǔ)句可以改善代碼的可讀性导而。
// 推薦
func eatDoughnut(atIndex index: Int) {
guard index >= 0 && index < doughnuts else {
// 如果 index 超出允許范圍忱叭,提前返回。
return
}
let doughnut = doughnuts[index]
eat(doughnut)
}
// 不推薦
func eatDoughnuts(atIndex index: Int) {
if index >= 0 && index < donuts.count {
let doughnut = doughnuts[index]
eat(doughnut)
}
}
- 在解析可選類(lèi)型時(shí)今艺,推薦使用 guard 語(yǔ)句韵丑,而不是 if 語(yǔ)句,因?yàn)?guard 語(yǔ)句可以減少不必要的嵌套縮進(jìn)虚缎。
// 推薦
guard let monkeyIsland = monkeyIsland else {
return
}
bookVacation(onIsland: monkeyIsland)
bragAboutVacation(onIsland: monkeyIsland)
// 不推薦
if let monkeyIsland = monkeyIsland {
bookVacation(onIsland: monkeyIsland)
bragAboutVacation(onIsland: monkeyIsland)
}
// 禁止
if monkeyIsland == nil {
return
}
bookVacation(onIsland: monkeyIsland!)
bragAboutVacation(onIsland: monkeyIsland!)
- 當(dāng)解析可選類(lèi)型需要決定在 if 語(yǔ)句 和 guard 語(yǔ)句之間做選擇時(shí)撵彻,最重要的判斷標(biāo)準(zhǔn)是是否讓代碼可讀性更強(qiáng),實(shí)際項(xiàng)目中會(huì)面臨更多的情景实牡,如依賴 2 個(gè)不同的布爾值陌僵,復(fù)雜的邏輯語(yǔ)句會(huì)涉及多次比較等,大體上說(shuō)创坞,根據(jù)你的判斷力讓代碼保持一致性和更強(qiáng)可讀性碗短, 如果你不確定 if 語(yǔ)句 和 guard 語(yǔ)句哪一個(gè)可讀性更強(qiáng),建議使用 guard 摆霉。
// if 語(yǔ)句更有可讀性
if operationFailed {
return
}
// guard 語(yǔ)句這里有更好的可讀性
guard isSuccessful else {
return
}
// 雙重否定不易被理解 - 不要這么做
guard !operationFailed else {
return
}
- 如果需要在2個(gè)狀態(tài)間做出選擇豪椿,建議使用if 語(yǔ)句奔坟,而不是使用 guard 語(yǔ)句携栋。
// 推薦
if isFriendly {
print("你好, 遠(yuǎn)路來(lái)的朋友!")
} else {
print(“窮小子咳秉,哪兒來(lái)的婉支?")
}
// 不推薦
guard isFriendly else {
print("窮小子,哪兒來(lái)的澜建?")
return
}
print("你好, 遠(yuǎn)路來(lái)的朋友向挖!")
- 你只應(yīng)該在在失敗情形下退出當(dāng)前上下文的場(chǎng)景下使用 guard 語(yǔ)句,下面的例子可以解釋 if 語(yǔ)句有時(shí)候比 guard 語(yǔ)句更合適 – 我們有兩個(gè)不相關(guān)的條件炕舵,不應(yīng)該相互阻塞何之。
if let monkeyIsland = monkeyIsland {
bookVacation(onIsland: monkeyIsland)
}
if let woodchuck = woodchuck where canChuckWood(woodchuck) {
woodchuck.chuckWood()
}
- 我們會(huì)經(jīng)常遇到使用 guard 語(yǔ)句拆包多個(gè)可選值,如果所有拆包失敗的錯(cuò)誤處理都一致可以把拆包組合到一起 (如 return, break, continue,throw 等)咽筋。
// 組合在一起因?yàn)榭赡芰⒓捶祷?guard let thingOne = thingOne,
let thingTwo = thingTwo,
let thingThree = thingThree else {
return
}
// 使用獨(dú)立的語(yǔ)句 因?yàn)槊總€(gè)場(chǎng)景返回不同的錯(cuò)誤
guard let thingOne = thingOne else {
throw Error(message: "Unwrapping thingOne failed.")
}
guard let thingTwo = thingTwo else {
throw Error(message: "Unwrapping thingTwo failed.")
}
guard let thingThree = thingThree else {
throw Error(message: "Unwrapping thingThree failed.")
}
文檔/注釋
- 文檔
*如果一個(gè)函數(shù)比 O(1) 復(fù)雜度高溶推,你需要考慮為函數(shù)添加注釋?zhuān)驗(yàn)楹瘮?shù)簽名(方法名和參數(shù)列表) 并不是那么的一目了然,這里推薦比較流行的插件 VVDocumenter. 不論出于何種原因,如果有任何奇淫巧計(jì)不易理解的代碼蒜危,都需要添加注釋?zhuān)瑢?duì)于復(fù)雜的 類(lèi)/結(jié)構(gòu)體/枚舉/協(xié)議/屬性 都需要添加注釋虱痕。所有公開(kāi)的 函數(shù)/類(lèi)/變量/枚舉/協(xié)議/屬性/常數(shù) 也都需要添加文檔,特別是 函數(shù)聲明(包括名稱(chēng)和參數(shù)列表) 不是那么清晰的時(shí)候辐赞。
在注釋文檔完成后部翘,你應(yīng)檢查格式是否正確。 - 注釋文檔規(guī)則如下:
一行不要超過(guò)160個(gè)字符 (和代碼長(zhǎng)度限制雷同)响委。
即使文檔注釋只有一行新思,也要使用模塊化格式 (/* /)。
注釋模塊中的空行不要使用 * 來(lái)占位赘风。
確定使用新的 – parameter 格式表牢,而不是就得 Use the new -:param: 格式,另外注意 parameter 是小寫(xiě)的贝次。
如果需要給一個(gè)方法的 參數(shù)/返回值/拋出異常 添加注釋?zhuān)瑒?wù)必給所有的添加注釋?zhuān)词箷?huì)看起來(lái)有部分重復(fù)崔兴,否則注釋會(huì)看起來(lái)不完整,有時(shí)候如果只有一個(gè)參數(shù)值得添加注釋?zhuān)梢栽诜椒ㄗ⑨尷镏攸c(diǎn)描述蛔翅。
對(duì)于負(fù)責(zé)的類(lèi)敲茄,在描述類(lèi)的使用方法時(shí)可以添加一些合適的例子,請(qǐng)注意Swift注釋是支持 MarkDown 語(yǔ)法的山析。
/**
## 功能列表
這個(gè)類(lèi)提供下一下很贊的功能堰燎,如下:
- 功能 1
- 功能 2
- 功能 3
## 例子
這是一個(gè)代碼塊使用四個(gè)空格作為縮進(jìn)的例子。
let myAwesomeThing = MyAwesomeClass()
myAwesomeThing.makeMoney()
## 警告
使用的時(shí)候總注意以下幾點(diǎn)
1. 第一點(diǎn)
2. 第二點(diǎn)
3. 第三點(diǎn)
*/
class MyAwesomeClass {
/* ... */
}
在寫(xiě)文檔注釋時(shí)笋轨,盡量保持簡(jiǎn)潔秆剪。
- 其他注釋原則
// 后面要保留空格。
注釋必須要另起一行爵政。
使用注釋 // MARK: - xoxo 時(shí), 下面一行保留為空行仅讽。
class Pirate {
// MARK: - 實(shí)例屬性
private let pirateName: String
// MARK: - 初始化
init() {
/* ... */
}
}