《Pro Swift》 第一章:語(yǔ)法(Syntax)

當(dāng)知道不需要重寫(xiě)聲明時(shí),對(duì)屬性和方法使用final君旦。 這允許編譯器用直接調(diào)用替換動(dòng)態(tài)派發(fā)調(diào)用贡蓖。甚至可以通過(guò)將屬性附加到類(lèi)本身,將整個(gè)類(lèi)標(biāo)記為final蘑志。
-- Wendy Lu (@wendyluwho),PinterestiOS 工程師

模式匹配

Switch/case不是一個(gè)新概念:插入一個(gè)值累奈,然后執(zhí)行幾個(gè)操作過(guò)程中的一個(gè)贬派。Swift 對(duì)安全的關(guān)注增加了對(duì)混編的要求,所有可能的情況都要滿足––如果沒(méi)有啟用特定的警告澎媒,你將無(wú)法在 C 中獲得某些信息搞乏,但這是相當(dāng)微不足道的。

Swiftswitch語(yǔ)法之所以有趣戒努,歸功于它靈活请敦、富有表現(xiàn)力的模式匹配。更有趣的是储玫,自從 Swift 發(fā)布以來(lái)侍筛,大部分模式匹配都被擴(kuò)展到了其他地方,所以在if條件和for循環(huán)中也可以使用同樣靈活撒穷、有表現(xiàn)力的語(yǔ)法匣椰。

不可否認(rèn),如果你在深水區(qū)跳入水中端礼,你更可能下沉而不是游泳禽笑,所以我想從基本的例子開(kāi)始進(jìn)行研究。為了刷新你的記憶齐媒,這里有一個(gè)基本的switch語(yǔ)句:

let name = "twostraws"
switch name {
case "bilbo":
   print("Hello, Bilbo Baggins!")
case "twostraws":
   print("Hello, Paul Hudson!")
default:
   print("Authentication failed")
}

當(dāng)你處理一個(gè)簡(jiǎn)單的字符串時(shí)蒲每,這非常簡(jiǎn)單,但是當(dāng)處理兩個(gè)或多個(gè)值時(shí)喻括,事情就變得更加復(fù)雜了邀杏。例如,如果我們想驗(yàn)證一個(gè)名稱(chēng)和密碼唬血,我們將把它們作為一個(gè)元組來(lái)計(jì)算:

let name = "twostraws"
let password = "fr0st1es"
switch (name, password) {
case ("bilbo", "bagg1n5"):
   print("Hello, Bilbo Baggins!")
case ("twostraws", "fr0st1es"):
   print("Hello, Paul Hudson!")
default:
   print("Who are you?")
}

如果你愿意望蜡,可以將這兩個(gè)值組合成一個(gè)元組,如下所示:

let authentication = (name: "twostraws", password: "fr0st1es")
switch authentication {
case ("bilbo", "bagg1n5"):
   print("Hello, Bilbo Baggins!")
case ("twostraws", "fr0st1es"):
   print("Hello, Paul Hudson!")
default:
   print("Who are you?")
}

在這種情況下拷恨,元組的兩個(gè)部分都必須與switch匹配才能執(zhí)行它脖律。

部分匹配

在處理元組時(shí),有時(shí)需要部分匹配:你關(guān)心某些值是什么腕侄,但不關(guān)心其他值小泉。在這種情況下,使用下劃線表示任何值都可以冕杠,如下所示:

let authentication = (name: "twostraws", password: "fr0st1es", ipAddress: "127.0.0.1")
switch authentication {
case ("bilbo", "bagg1n5", _):
   print("Hello, Bilbo Baggins!")
case ("twostraws", "fr0st1es", _):
   print("Hello, Paul Hudson!")
default:
   print("Who are you?")
}

記孜㈡ⅰ:Swift 將采用它發(fā)現(xiàn)的第一個(gè)匹配的案例,因此你需要確保首先查找最具體的內(nèi)容分预。例如兢交,下面的代碼將打印 You could be anybody! 因?yàn)榈谝环N情況會(huì)立即匹配,即使之后的案例會(huì)更好匹配笼痹,因?yàn)樗鼈兤ヅ涞膬?nèi)容更多:

switch authentication {
case (_, _, _):
   print("You could be anybody!")
case ("bilbo", "bagg1n5", _):
   print("Hello, Bilbo Baggins!")
case ("twostraws", "fr0st1es", _):
   print("Hello, Paul Hudson!")
default:
   print("Who are you?")
}

最后配喳,如果你只想匹配元組的一部分酪穿,但仍然想知道另一部分是什么,那么應(yīng)該使用let語(yǔ)法晴裹。

switch authentication {
case ("bilbo", "bagg1n5", _):
   print("Hello, Bilbo Baggins!")
case ("twostraws", let password, _):
   print("Hello, Paul Hudson: your password was \(password)!")
default:
   print("Who are you?")
}

匹配計(jì)算元組

到目前為止被济,我們的介紹已經(jīng)覆蓋了大多數(shù)開(kāi)發(fā)人員使用模式匹配語(yǔ)法的基本范圍。從這里開(kāi)始息拜,我想舉一些其他不太為人所知的有用的模式匹配技術(shù)的例子溉潭。

元組最常使用靜態(tài)值創(chuàng)建净响,如下所示:

let name = ("Paul", "Hudson")

但元組和其他任何數(shù)據(jù)結(jié)構(gòu)一樣少欺,都可以使用動(dòng)態(tài)代碼創(chuàng)建。當(dāng)你希望將元組中的值范圍縮小到較小的子集馋贤,以便只需要少數(shù)case語(yǔ)句時(shí)赞别,這一點(diǎn)特別有用。

為了給你一個(gè)實(shí)際的例子配乓,考慮 fizz buzz 測(cè)試:編寫(xiě)一個(gè)接受任何數(shù)字的函數(shù)仿滔,如果數(shù)字被 3 整除,則返回 fizz犹芹;如果數(shù)字被 5 整除崎页,則返回 buzz;如果數(shù)字被 35 整除腰埂,則返回fizzbuzz飒焦;在其他情況下,返回原始輸入數(shù)字屿笼。

我們可以計(jì)算一個(gè)元組來(lái)解決這個(gè)問(wèn)題牺荠,然后將該元組傳遞到一個(gè)switch中以創(chuàng)建正確的輸出。代碼如下:

func fizzbuzz(number: Int) -> String {
   switch (number % 3 == 0, number % 5 == 0) {
   case (true, false):
      return "Fizz"
   case (false, true):
      return "Buzz"
   case (true, true):
      return "FizzBuzz"
   case (false, false):
      return String(number)
   }
}
print(fizzbuzz(number: 15))

這種方法將大的輸入空間(任何數(shù)字)分解為真和假的簡(jiǎn)單組合驴一,然后在case語(yǔ)句中使用元組模式匹配來(lái)選擇正確的輸出休雌。

循環(huán)(Loops)

正如你所看到的,使用元組的一部分進(jìn)行模式匹配非常簡(jiǎn)單:你可以告訴 Swift 應(yīng)該匹配什么肝断,使用let將值綁定到局部常量杈曲,或者使用_表示你不關(guān)心值是什么。

在處理循環(huán)時(shí)胸懈,我們可以使用相同的方法担扑,這允許我們只在項(xiàng)與我們指定的條件匹配時(shí)遍歷項(xiàng)。讓我們?cè)俅螐囊粋€(gè)基本示例開(kāi)始:

let twostraws = (name: "twostraws", password: "fr0st1es")
let bilbo = (name: "bilbo", password: "bagg1n5")
let taylor = (name: "taylor", password: "fr0st1es")
let users = [twostraws, bilbo, taylor]
for user in users {
   print(user.name)
}

這將創(chuàng)建一個(gè)元組數(shù)組箫荡,然后循環(huán)遍歷每個(gè)元組并打印其name的值魁亦。

就像我們前面看到的switch塊一樣,我們可以用case和元組來(lái)匹配
元組中的特定值羔挡。在之前的循環(huán)下面添加以下代碼:

for case ("twostraws", "fr0st1es") in users {
   print("User twostraws has the password fr0st1es")
}

我們也有相同的語(yǔ)法來(lái)將局部常量綁定到每個(gè)元組的值洁奈,例如:

for case (let name, let password) in users {
   print("User \(name) has the password \(password)")
}

不過(guò)间唉,通常情況下,最好將let重新放置為:

for case let (name, password) in users {
   print("User \(name) has the password \(password)")
}

神奇的是利术,當(dāng)你把這兩種方法結(jié)合起來(lái)的時(shí)候呈野,在語(yǔ)法上和我們已經(jīng)看到的switch例子是一樣的:

for case let (name, "fr0st1es") in users {
   print("User \(name) has the password \"fr0st1es\"")
}

它過(guò)濾users數(shù)組,這樣只有密碼為fr0st1es的項(xiàng)才會(huì)在循環(huán)中使用印叁,然后在循環(huán)中創(chuàng)建一個(gè)name常量供你使用被冒。

如果你正盯著for case let,看到三個(gè)完全不同的關(guān)鍵字混在一起轮蜕,不要擔(dān)心:除非有人向你解釋它昨悼,否則它做什么并不明顯,而且要花一點(diǎn)時(shí)間才能理解跃洛。但我們才剛剛開(kāi)始...

匹配可選值(Matching optionals)

Swift 有兩種匹配可選值的方法率触,你很可能會(huì)同時(shí)遇到這兩種方法。首先是
使用.some.none來(lái)匹配有值沒(méi)有值汇竭,在下面的代碼中葱蝗,它用于檢查值并將它們綁定到本地常量:

let name: String? = "twostraws"
let password: String? = "fr0st1es"
switch (name, password) {
case let (.some(name), .some(password)):
   print("Hello, \(name)")
case let (.some(name), .none):
   print("Please enter a password.")
default:
   print("Who are you?")
}

由于namepassword用于輸入常量和本地綁定的常量,這段代碼變得更加容易混淆细燎。但是两曼,它們是不同的東西,這就是為什么 print("Hello,\(name)") 不會(huì)打印 Hello, Optional("twostraws") —— 這里所使用的name是本地綁定展開(kāi)的可選名稱(chēng)玻驻。

如果想更容易閱讀悼凑,下面是相同的代碼,不同的名稱(chēng)用于匹配的常量:

switch (name, password) {
case let (.some(matchedName), .some(matchedPassword)):
   print("Hello, \(matchedName)")
case let (.some(matchedName), .none):
   print("Please enter a password.")
default:
   print("Who are you?")
}

Swift 匹配可選值的第二種方法是使用更簡(jiǎn)單的語(yǔ)法击狮,盡管如果你害怕可選值佛析,這只會(huì)讓情況變得更糟:

switch (name, password) {
case let (name?, password?):
   print("Hello, \(name)")
case let (username?, nil):
   print("Please enter a password.")
default:
   print("Who are you?")
}

這一次問(wèn)號(hào)的工作方式與可選鏈類(lèi)似:僅當(dāng)找到值時(shí)才繼續(xù)

這兩種方法在if case let代碼中都同樣有效彪蓬。下面的代碼同時(shí)使用它們?cè)谘h(huán)中過(guò)濾掉的nil值:

import Foundation
let data: [Any?] = ["Bill", nil, 69, "Ted"]
for case let .some(datum) in data {
   print(datum)
}
for case let datum? in data {
   print(datum)
}

匹配范圍(Matching ranges)

你可能已經(jīng)在使用與范圍匹配的模式寸莫,通常使用如下代碼:

let age = 36
switch age {
case 0 ..< 18:
   print("You have the energy and time, but not the money")
case 18 ..< 70:
   print("You have the energy and money, but not the time")
default:
   print("You have the time and money, but not the energy")
}

對(duì)于常規(guī)條件語(yǔ)句,也可以使用非常相似的語(yǔ)法–我們可以這樣重寫(xiě)代碼:

if case 0 ..< 18 = age {
   print("You have the energy and time, but not the money")
} else if case 18 ..< 70 = age {
   print("You have the energy and money, but not the time")
} else {
   print("You have the time and money, but not the energy")
}

在使用類(lèi)似語(yǔ)法的情況下档冬,這將產(chǎn)生與switch塊相同的結(jié)果膘茎,但我不太喜歡這種方法。我不喜歡它的原因很簡(jiǎn)單:我不認(rèn)為如果0到18的范圍等于年齡是合理的酷誓,如果你還不知道它這意味著什么披坏。一個(gè)更好的方法是使用模式匹配操作符~=,它看起來(lái)像這樣:

if 0 ..< 18 ~= age {
   print("You have the energy and time, but not the money")
} else if 18 ..< 70 ~= age {
   print("You have the energy and money, but not the time")
} else {
   print("You have the time and money, but not the energy")
}

現(xiàn)在條件是如果0到18的范圍與年齡相匹配盐数,我認(rèn)為這更有意義棒拂。

當(dāng)你記住0..<18創(chuàng)建了一個(gè)具有自己方法集的Range結(jié)構(gòu)的實(shí)例時(shí),一個(gè)更清晰的解決方案就浮出水面了。現(xiàn)在帚屉,它的contains()方法特別有用:它的輸入時(shí)間比~=長(zhǎng)谜诫,但更容易理解:

if (0 ..< 18).contains(age) {
   print("You have the energy and time, but not the money")
} else if (18 ..< 70).contains(age) {
   print("You have the energy and money, but not the time")
} else {
   print("You have the time and money, but not the energy")
}

你可以將此范圍匹配組合到現(xiàn)有的元組匹配代碼中,如下所示:

let user = (name: "twostraws", password: "fr0st1es", age: 36)
switch user {
case let (name, _, 0 ..< 18):
   print("\(name) has the energy and time, but no money")
case let (name, _, 18 ..< 70):
   print("\(name) has the money and energy, but no time")
case let (name, _, _):
   print("\(name) has the time and money, but no energy")
}

最后一種情況將用戶名綁定到名為name的本地常量攻旦,而不考慮元組中的其他兩個(gè)值喻旷。這是一個(gè)匹配所有的case,但因?yàn)?Swift 尋找第一個(gè)匹配的case牢屋,這不會(huì)與switch塊中的其他兩個(gè)沖突赵刑。

匹配枚舉和關(guān)聯(lián)值(Matching enums and associated values)

根據(jù)我的經(jīng)驗(yàn)驹闰,相當(dāng)多的人并不真正理解枚舉和關(guān)聯(lián)值凡人,因此他們很難利用它們進(jìn)行模式匹配详拙。本書(shū)后面有一整章都是關(guān)于枚舉的,所以如果你對(duì)枚舉和相關(guān)的值還不熟悉的話皱炉,你可能需要在這里暫停并首先閱讀該章節(jié)怀估。

基本枚舉匹配是這樣的:

enum WeatherType {
   case cloudy
   case sunny
   case windy 
}

let today = WeatherType.cloudy

switch today {
case .cloudy:
   print("It's cloudy")
case .windy:
   print("It's windy")
default:
   print("It's sunny")
}

你還將在基本條件語(yǔ)句中使用枚舉,如下所示:

if today == .cloudy {
   print("It's cloudy")
}

一旦你添加了關(guān)聯(lián)值合搅,事情就會(huì)變得更加復(fù)雜,因?yàn)槟憧梢允褂盟鼈兤缃叮^(guò)濾它們灾部,或者根據(jù)你的目標(biāo)忽略它們。

首先惯退,最簡(jiǎn)單的選項(xiàng):創(chuàng)建關(guān)聯(lián)值赌髓,但忽略它:

enum WeatherType {
   case cloudy(coverage: Int)
   case sunny
   case windy
}
let today = WeatherType.cloudy(coverage: 100)

switch today {
case .cloudy:
   print("It's cloudy")
case .windy:
   print("It's windy")
default:
   print("It's sunny")
}

使用這種方法,實(shí)際上switch代碼是不變的催跪。

第二:創(chuàng)建關(guān)聯(lián)值并使用它锁蠕。這使用了我們已經(jīng)多次看到的相同的本地常量綁定:

enum WeatherType {
   case cloudy(coverage: Int)
   case sunny
   case windy
}

let today = WeatherType.cloudy(coverage: 100)

switch today {
case .cloudy(let coverage):
   print("It's cloudy with \(coverage)% coverage")
case .windy:
   print("It's windy")
default:
   print("It's sunny")
}

最后:創(chuàng)建一個(gè)關(guān)聯(lián)類(lèi)型,將一個(gè)本地常量綁定到該類(lèi)型懊蒸,同時(shí)使用該綁定來(lái)篩選特定的值荣倾。它使用where關(guān)鍵字創(chuàng)建一個(gè)需求子句,以闡明你要查找的內(nèi)容骑丸。在我們的例子中舌仍,下面的代碼根據(jù)cloudy使用的關(guān)聯(lián)值打印了兩條不同的消息:

enum WeatherType {
   case cloudy(coverage: Int)
   case sunny
   case windy
}

let today = WeatherType.cloudy(coverage: 100)

switch today {
case .cloudy(let coverage) where coverage < 100:
   print("It's cloudy with \(coverage)% coverage")
case .cloudy(let coverage) where coverage == 100:
   print("You must live in the UK")
case .windy:
   print("It's windy")
default:
   print("It's sunny")
}

現(xiàn)在,正如我所承諾的通危,我將從基本示例開(kāi)始構(gòu)建铸豁,但是如果你已經(jīng)準(zhǔn)備好了,我想向你展示如何將這兩種技術(shù)組合在一起:關(guān)聯(lián)值和范圍匹配菊碟。下面的代碼現(xiàn)在打印了四條不同的消息:

enum WeatherType {
   case cloudy(coverage: Int)
   case sunny
   case windy
}
let today = WeatherType.cloudy(coverage: 100)
switch today {
case .cloudy(let coverage) where coverage == 0:
   print("You must live in Death Valley")
case .cloudy(let coverage) where (1...50).contains(coverage):
   print("It's a bit cloudy, with \(coverage)% coverage")
case .cloudy(let coverage) where (51...99).contains(coverage):
   print("It's very cloudy, with \(coverage)% coverage")
case .cloudy(let coverage) where coverage == 100:
   print("You must live in the UK")
case .windy:
   print("It's windy")
default:
   print("It's sunny")
}

如果要匹配循環(huán)中的關(guān)聯(lián)值节芥,添加where子句是錯(cuò)誤的方法。事實(shí)上逆害,這類(lèi)代碼甚至無(wú)法編譯:

let forecast: [WeatherType] = [.cloudy(coverage:40), .sunny, .windy, .cloudy(coverage: 100), .sunny]
for day in forecast where day == .cloudy {
   print(day)
}

如果沒(méi)有關(guān)聯(lián)值头镊,該代碼就可以了增炭,但是因?yàn)殛P(guān)聯(lián)值意味著where子句不能勝任工作--它無(wú)法說(shuō)并將關(guān)聯(lián)的值綁定到本地常量。相反拧晕,你將回到case-let語(yǔ)法隙姿,如下所示:

let forecast: [WeatherType] = [.cloudy(coverage:40), .sunny, .windy, .cloudy(coverage: 100), .sunny]
for case let .cloudy(coverage) in forecast {
   print("It's cloudy with \(coverage)% coverage")
}

如果你想知道關(guān)聯(lián)值,并想把它用作過(guò)濾條件厂捞,語(yǔ)法幾乎是一樣的:

let forecast: [WeatherType] = [.cloudy(coverage: 40), .sunny, .windy, .cloudy(coverage: 100), .sunny]
for case .cloudy(40) in forecast {
   print("It's cloudy with 40% coverage")
}

匹配類(lèi)型(Matching types)

你應(yīng)該已經(jīng)知道用于匹配的is關(guān)鍵字输玷,但是你可能不知道它可以在循環(huán)和switch塊中用于模式匹配。我覺(jué)得這個(gè)語(yǔ)法很好用靡馁,所以我想簡(jiǎn)單說(shuō)明一下:

let view: AnyObject = UIButton()
switch view {
case is UIButton:
   print("Found a button")
case is UILabel:
   print("Found a label")
case is UISwitch:
   print("Found a switch")
case is UIView:
   print("Found a view")
default:
   print("Found something else")
}

我以 UIKit 為例欲鹏,因?yàn)槟銘?yīng)該已經(jīng)知道UIButton繼承自UIView,我需要給你一個(gè)大大的警告…

記住Swift 將接受它找到的第一個(gè)匹配的情況臭墨,如果對(duì)象是特定類(lèi)型或其父類(lèi)之一赔嚎,則is返回true。因此胧弛,上面的代碼將打印 Found a button尤误,而下面的代碼將打印 Found a view

let view: AnyObject = UIButton()
switch view {
case is UIView:
   print("Found a view")
case is UIButton:
   print("Found a button")
case is UILabel:
   print("Found a label")
case is UISwitch:
   print("Found a switch")
default:
   print("Found something else")
}

為了給你提供一個(gè)更有用的示例,你可以使用此方法循環(huán)數(shù)組中的所有子視圖并篩選UILabel

for label in view.subviews where label is UILabel {
   print("Found a label with frame \(label.frame)")
}

盡管where確保在循環(huán)中只處理UILabel對(duì)象结缚,但實(shí)際上它不執(zhí)行任何類(lèi)型轉(zhuǎn)換损晤。這意味著,如果你想訪問(wèn)label的特定屬性红竭,比如它的text屬性尤勋,你需要自己對(duì)它進(jìn)行類(lèi)型轉(zhuǎn)換。在這種情況下茵宪,使用for case let更容易最冰,因?yàn)樗谶^(guò)濾的同時(shí)進(jìn)行類(lèi)型轉(zhuǎn)換:

for case let label as UILabel in view.subviews {
   print("Found a label with text \(label.text)")
}

使用WHERE關(guān)鍵詞(Using the where keyword)

為了總結(jié)模式匹配,我想演示一些有趣的使用where子句的方法稀火,以便你了解它的功能暖哨。

首先,一個(gè)簡(jiǎn)單的方法:循環(huán)一組數(shù)字憾股,只打印奇數(shù)鹿蜀。使用where和取模運(yùn)算這很簡(jiǎn)單,但它表明where子句可以包含計(jì)算:

for number in numbers where number % 2 == 1 {
   print(number)
}

也可以調(diào)用方法服球,如:

let celebrities = ["Michael Jackson", "Taylor Swift", "Michael Caine", "Adele Adkins", "Michael Jordan"]
for name in celebrities where !name.hasPrefix("Michael") {
   print(name)
}

這將打印 Taylor SwiftAdele Adkins茴恰。如果要使where子句更復(fù)雜,只需添加&&等運(yùn)算符即可斩熊。

let celebrities = ["Michael Jackson", "Taylor Swift", "Michael Caine", "Adele Adkins", "Michael Jordan"]
for name in celebrities where name.hasPrefix("Michael") &&
name.characters.count == 13 {
   print(name)
}

這將打印 Michael Caine往枣。

雖然可以使用where子句剔除可選值,但我不推薦。請(qǐng)考慮下面的示例:

let celebrities: [String?] = ["Michael Jackson", nil, "Michael Caine", nil, "Michael Jordan"]
for name in celebrities where name != nil {
   print(name)
}

這當(dāng)然有用分冈,但它對(duì)循環(huán)中的字符串的可選性沒(méi)有任何影響圾另,所以它輸出如下:

Optional("Michael Jackson")
Optional("Michael Caine")
Optional("Michael Jordan")

相反,使用for case let來(lái)處理可選值雕沉,并使用where來(lái)過(guò)濾值集乔。下面是編寫(xiě)循環(huán)的首選方法:

for case let name? in celebrities where name.hasSuffix("Jackson") {
    print(name)
}

運(yùn)行時(shí),name只包含具有值且后綴為Jackson的字符串坡椒,因此其輸出為:

Michael Jackson

空值合并(Nil coalescing)

Swift 可選值是保證程序安全的基本方法之一:只有在變量確實(shí)具有值時(shí)才能使用它扰路。問(wèn)題是可選值使代碼更難讀和寫(xiě),因?yàn)槟阈枰踩卣归_(kāi)它們倔叼。

一種替代方法是使用!顯式地展開(kāi)可選值汗唱。這也被稱(chēng)為崩潰操作符,因?yàn)槿绻阍谥禐?code>nil的可選值上使用!丈攒,你的程序?qū)⒘⒓幢紳ⅰ?/p>

一個(gè)更聰明的選擇是空合運(yùn)算符??哩罪,它允許你訪問(wèn)可選值,并在可選值為nil時(shí)提供默認(rèn)值巡验。

考慮這個(gè)可選值:

let name: String? = "Taylor"

這是一個(gè)名為name的常量际插,包含一個(gè)字符串或nil。如果你試圖用print(name)打印它深碱,你會(huì)看到Optional("Taylor")腹鹉,而不是"Taylor",這不是你真正想要的敷硅。

使用空值合并允許我們使用可選的值,或者提供一個(gè)默認(rèn)值(如果為nil)愉阎。所以绞蹦,你可以這樣寫(xiě):

let name: String? = "Taylor"
let unwrappedName = name ?? "Anonymous"
print(unwrappedName)

這將打印"Taylor": nameString?,但是unwrappedName保證是一個(gè)常量字符串(不是可選的)榜旦,因?yàn)槭褂昧丝蘸线\(yùn)算符幽七。要查看默認(rèn)值的實(shí)際操作,請(qǐng)嘗試以下操作:

let name: String? = nil
let unwrappedName = name ?? "Anonymous"
print(unwrappedName)

現(xiàn)在將打印"Anonymous"溅呢,因?yàn)槭褂玫氖悄J(rèn)值澡屡。

當(dāng)然,當(dāng)使用空值合并時(shí)咐旧,你不需要一個(gè)單獨(dú)的常量——你可以在內(nèi)聯(lián)中編寫(xiě)它驶鹉,如下所示:

let name: String? = "Taylor"
print(name ?? "Anonymous")

正如您可以想象的那樣,空值合并對(duì)于確保在使用前合理的值已經(jīng)就位是非常非常有用的铣墨,但是它對(duì)于從代碼中刪除一些可選性特別有用室埋。例如:

func returnsOptionalName() -> String? {
   return nil
}
let returnedName = returnsOptionalName() ?? "Anonymous"
print(returnedName)

使用這種方法,returnedNameString而不是String?, 因?yàn)樗怯兄档摹?/p>

到目前為止,一切都很簡(jiǎn)單姚淆。然而孕蝉,當(dāng)你將空值合并與try? 關(guān)鍵字結(jié)合起來(lái)時(shí),它會(huì)變得更加有趣腌逢。

考慮一個(gè)簡(jiǎn)單的應(yīng)用程序降淮,它允許用戶鍵入并保存文本。當(dāng)應(yīng)用程序運(yùn)行時(shí)搏讶,它希望加載用戶以前鍵入的任何內(nèi)容佳鳖,因此它可能使用如下代碼:

do {
   let savedText = try String(contentsOfFile: "saved.txt")
   print(savedText)
} catch {
   print("Failed to load saved text.")
}

如果文件存在,它將加載到savedText常量中窍蓝。否則 contentsOfFile初始化器將拋出異常腋颠,并打印未能加載保存的文本。在實(shí)際中吓笙,你想要擴(kuò)展它淑玫,以便savedText總是有一個(gè)值,所以你最終得到這樣的結(jié)果:

let savedText: String
do {
   savedText = try String(contentsOfFile: "saved.txt")
} catch {
   print("Failed to load saved text.")
   savedText = "Hello, world!"
}
print(savedText)

這是很多代碼面睛,但實(shí)際上并沒(méi)有完成很多工作絮蒿。幸運(yùn)的是,有一種更好的方法:空值合并叁鉴。記住土涝,try有三種變體:try嘗試一些代碼,并可能引發(fā)異常幌墓,try但壮!嘗試一些代碼并在應(yīng)用程序失敗時(shí)崩潰,然后try?嘗試一些代碼常侣,如果調(diào)用失敗蜡饵,則返回nil

最后一個(gè)例子是空值合并的使用場(chǎng)景胳施,因?yàn)檫@與前面的示例完全匹配:我們希望使用可選值溯祸,如果可選值為nil,則提供合理的默認(rèn)值舞肆。事實(shí)上焦辅,使用空值合并,我們可以將所有這些代碼重寫(xiě)為兩行代碼:

let savedText = (try? String(contentsOfFile: "saved.txt")) ?? "Hello, world!"
print(savedText)

這意味著嘗試加載文件椿胯,但如果加載失敗筷登,則使用此默認(rèn)文本——更整潔的解決方案,可讀性更高压状。

結(jié)合try?和空值合并非常適合于失敗的try不是錯(cuò)誤的情況仆抵,我認(rèn)為你會(huì)發(fā)現(xiàn)這種模式在你自己的代碼中非常有用跟继。

Guard

Swift 2.0 開(kāi)始,guard這個(gè)關(guān)鍵字就一直伴隨著我們镣丑,但是因?yàn)樗淮涡酝瑫r(shí)做了四件事舔糖,所以你沒(méi)有充分使用它是值得原諒的。

第一種用法是最明顯的:guard用于早期返回莺匠,這意味著如果某些前提條件不滿足金吗,則退出函數(shù)。例如趣竣,我們可以編寫(xiě)一個(gè)帶有偏見(jiàn)的函數(shù)來(lái)給一個(gè)指定的人頒獎(jiǎng):

func giveAward(to name: String) {
   guard name == "Taylor Swift" else {
      print("No way!")
      return
   }
   print("Congratulations, \(name)!")
}
giveAward(to: "Taylor Swift")

使用giveAward(to:)方法中的guard聲明可以確保只有Taylor Swift贏得獎(jiǎng)項(xiàng)摇庙。正如我所說(shuō),這是有偏見(jiàn)的遥缕,但是前提條件是明確的卫袒,并且只有在滿足我設(shè)置的需求時(shí),這段代碼才會(huì)運(yùn)行单匣。

這個(gè)最初的示例看起來(lái)幾乎與使用if相同夕凝,但是guard有一個(gè)巨大的優(yōu)勢(shì):它使你的意圖變得清晰,不僅對(duì)人們户秤,而且對(duì)編譯器也是如此码秉。這是一個(gè)早期返回,也就是說(shuō)鸡号,如果不滿足前提條件转砖,你希望退出該方法。使用guard可以清楚地表明:這種情況不是方法功能的一部分鲸伴,只是為了確保實(shí)際的代碼可以安全運(yùn)行府蔗。編譯器也很清楚,這意味著如果刪除return語(yǔ)句汞窗,代碼將不再生成—— Swift 知道這是一個(gè)早期返回礁竞,因此它不會(huì)讓你忘記退出。

使用guard的第二個(gè)好處是第一個(gè)好處的副作用:使用guard和早期返回可以降低縮進(jìn)級(jí)別杉辙。一些開(kāi)發(fā)人員堅(jiān)信不能使用早期的返回,相反捶朵,每個(gè)函數(shù)應(yīng)該只從一個(gè)地方返回蜘矢。會(huì)在函數(shù)代碼的主體中強(qiáng)制進(jìn)行額外的縮進(jìn),如下所示:

func giveAward(to name: String) -> String {
   let message: String
   if name == "Taylor Swift" {
      message = "Congratulations, \(name)!"
   } else {
      message = "No way!"
   }
   return message
}
giveAward(to: "Taylor Swift")

有了guard综看,你的先決條件立刻得到解決品腹,并丟棄額外的縮進(jìn)——為整齊的代碼歡呼!

guard帶給我們的第三件事是提高主邏輯的明顯性。這是軟件設(shè)計(jì)和測(cè)試中的一個(gè)常見(jiàn)概念红碑,指的是當(dāng)沒(méi)有異澄杩裕或錯(cuò)誤發(fā)生時(shí)泡垃,代碼將采用的路徑。由于有了guard羡鸥,常見(jiàn)的錯(cuò)誤會(huì)立即被刪除蔑穴,代碼的其余部分可能都是正確的路徑。

這些都是很簡(jiǎn)單的事情惧浴,但是guard還有一個(gè)我想要討論的特性存和,它是guardif之間的一個(gè)重要區(qū)別:當(dāng)你使用guard檢查和解包一個(gè)可選值時(shí),這個(gè)可選值會(huì)留在作用域中衷旅。

為了演示這一點(diǎn)捐腿,我將重寫(xiě)giveAward(To:)方法,使它接受一個(gè)可選的字符串:

func giveAward(to name: String?) {
   guard let winner = name else {
      print("No one won the award")
      return
    }
   print("Congratulations, \(winner)!")
}

如果使用常規(guī)的if-let柿顶,winner常量只能在屬于guard的大括號(hào)內(nèi)使用茄袖。然而,guard在作用域中保持其可選的展開(kāi)嘁锯,因此winner將在第二個(gè)print()語(yǔ)句中保留宪祥。這段代碼讀作嘗試將name解包到winner,這樣我就可以使用它了猪钮,但是如果你不能品山,那么就打印一條消息并退出。

我想介紹一下guard的最后一個(gè)特性烤低,但它并不新鮮肘交。相反,它只是一種使用你已經(jīng)知道的東西的不同方式扑馁。特性是:如果前提條件失敗涯呻,guard允許你退出任何范圍,而不僅僅是函數(shù)和方法腻要。這意味著你可以使用guard退出switch塊或循環(huán)复罐,它具有相同的含義:只有當(dāng)這些前提條件為真時(shí),才應(yīng)執(zhí)行此范圍的內(nèi)容雄家。

舉個(gè)簡(jiǎn)單的例子效诅,這個(gè)循環(huán)從 1100,打印出所有能被 8 整除的數(shù):

for i in 1...100 {
   guard i % 8 == 0 else { continue }
   print(i) 
}

你能用where重寫(xiě)一下嗎趟济?試一試——它比你想象的要容易乱投!

// 答案
for i in 1...100 where  i % 8 == 0 {
    print(i)
}

懶加載(Lazy loading)

延遲加載是 Swift 編碼人員進(jìn)行的最重要的全系統(tǒng)性能優(yōu)化之一。它在 iOS 中很普遍顷编,任何試圖在視圖控制器的視圖顯示之前操縱它的人都能告訴你戚炫。Objective-C 沒(méi)有惰性屬性的概念,因此每次需要這種行為時(shí)媳纬,都必須編寫(xiě)自己的樣板代碼双肤。令人欣慰的是施掏,Swift 已經(jīng)將它完全融入其中,所以你幾乎不需要任何代碼就可以立即獲得性能優(yōu)勢(shì)茅糜。

但首先:提醒一下什么是惰性屬性七芭。考慮一下這個(gè)類(lèi):

class Singer {
   let name: String
   init(name: String) {
      self.name = name
   }   
   func reversedName() -> String {
      return "\(name.uppercased()) backwards is \(String(name.uppercased().characters.reversed()))!"
   } 
}
let taylor = Singer(name: "Taylor Swift")
print(taylor.reversedName())

這將在運(yùn)行時(shí)打印 TAYLOR SWIFT backwards is TFIWS ROLYAT!限匣。

每個(gè)Singer都有一個(gè)名為name的屬性抖苦,還有一個(gè)方法對(duì)這個(gè)屬性進(jìn)行少量處理。顯然米死,在你自己的代碼中锌历,這些函數(shù)可能會(huì)做更重要的工作,但是我在這里盡量保持簡(jiǎn)單峦筒。

每次你想打印消息 TAYLOR SWIFT reverse is TFIWS ROLYAT! 時(shí)究西,你都需要調(diào)用reversedName()方法——它所做的工作不會(huì)被存儲(chǔ),如果該工作不是瑣碎的物喷,那么重復(fù)調(diào)用該方法就是浪費(fèi)卤材。

另一種方法是創(chuàng)建一個(gè)額外的屬性來(lái)存儲(chǔ)reversedName,這樣它只計(jì)算一次峦失,如下所示:

class Singer {
   let name: String
   let reversedName: String
   init(name: String) {
      self.name = name
      reversedName = "\(name.uppercased()) backwards is \(String(name.uppercased().characters.reversed()))!"
} }
let taylor = Singer(name: "Taylor Swift")
print(taylor.reversedName)

對(duì)于經(jīng)常使用reversedName的情況扇丛,這是一種性能改進(jìn),但是如果從不使用reversedName尉辑,則會(huì)導(dǎo)致代碼運(yùn)行得更慢——無(wú)論是否使用帆精,都會(huì)計(jì)算它,而當(dāng)reversedName()是一個(gè)方法時(shí)隧魄,它只會(huì)在調(diào)用時(shí)計(jì)算卓练。

惰性屬性是中間地帶:它們是只計(jì)算一次并存儲(chǔ)的屬性,但是如果不使用它們购啄,就永遠(yuǎn)不會(huì)計(jì)算襟企。因此,如果你的代碼重復(fù)使用惰性屬性狮含,那么只需要付出一次性能代價(jià)顽悼,如果這些屬性從未使用過(guò),那么代碼就永遠(yuǎn)不會(huì)運(yùn)行几迄。這是雙贏的!

懶閉包(Lazy closures)

開(kāi)始使用lazy關(guān)鍵字的最簡(jiǎn)單方法是使用閉包表蝙。 是的,我知道在同一個(gè)句子中看到 closures(閉包)easiest(最簡(jiǎn)單的) 的情況很少見(jiàn)乓旗,但有一個(gè)原因,這本書(shū)不叫Newbie Swift(新手Swift)集索!

這里的語(yǔ)法一開(kāi)始有點(diǎn)不尋常:

lazy var yourVariableName: SomeType = {
   return SomeType(whatever: "foobar")
}()

是的屿愚,你需要顯式地聲明類(lèi)型汇跨。是的,你需要那個(gè)=號(hào)妆距。是的穷遂,你需要在右大括號(hào)后面加上小括號(hào)。這有點(diǎn)不尋常娱据,就像我說(shuō)的蚪黑,但它的存在是有原因的:你正在創(chuàng)建閉包,立即應(yīng)用它(而不是稍后)中剩,并將其結(jié)果分配回variablename忌穿。

使用這種方法,我們可以將reversedName()方法轉(zhuǎn)換成如下所示的惰性屬性:

class Singer {
   let name: String
   init(name: String) {
      self.name = name
   }
   lazy var reversedName: String = {
      return "\(self.name.uppercased()) backwards is \(String(self.name.uppercased().characters.reversed()))!"
   }() 
}
let taylor = Singer(name: "Taylor Swift")
print(taylor.reversedName)

注意:由于它現(xiàn)在是一個(gè)屬性而不是方法结啼,所以我們需要使用print(taylor.reversedName)而不是print(taylor.reversedName())來(lái)訪問(wèn)該值掠剑。

就是這樣:屬性現(xiàn)在是惰性的,這意味著只有在第一次讀取reversedName屬性時(shí)才會(huì)執(zhí)行閉包中的代碼郊愧。

但保羅朴译,” 我聽(tīng)到你說(shuō),“你在一個(gè)由對(duì)象擁有的閉包中使用self - 為什么你給我一個(gè)循環(huán)引用属铁?” 別擔(dān)心:這段代碼非常安全眠寿。 Swift 聰明到足以意識(shí)到正在發(fā)生的事情,并且不會(huì)創(chuàng)建任何循環(huán)引用焦蘑。

在引擎下盯拱,任何這樣的閉包 - 立即應(yīng)用的 - 被認(rèn)為是 non-escaping(非逃逸),在我們的情況下意味著它不會(huì)在其他任何地方使用喇肋。 也就是說(shuō)坟乾,此閉包不能存儲(chǔ)為屬性并稍后調(diào)用。 這不僅會(huì)自動(dòng)確保self被認(rèn)為是unowned蝶防,而且還使 Swift 編譯器能夠進(jìn)行一些額外的優(yōu)化甚侣,因?yàn)樗懈嚓P(guān)于閉包的信息。

懶方法(Lazy methods)

在使用lazy時(shí)间学,人們經(jīng)常抱怨它會(huì)使代碼變得混亂:lazy屬性不是屬性和方法的簡(jiǎn)單分離殷费,而是屬性和功能混合在一起的灰色區(qū)域。對(duì)此有一個(gè)簡(jiǎn)單的解決方案:創(chuàng)建方法來(lái)將惰性屬性從它們所依賴(lài)的代碼中分離出來(lái)低葫。

如果你想使用這種方法详羡,我建議你將創(chuàng)建的單獨(dú)方法標(biāo)記為private,這樣就不會(huì)意外地使用它嘿悬。類(lèi)似這樣的東西應(yīng)該會(huì)奏效:

class Singer {
   let name: String
   init(name: String) {
      self.name = name
  }
   lazy var reversedName: String = self.getReversedName()
   private func getReversedName() -> String {
      return "\(name.uppercased()) backwards is \(String(name.uppercased().characters.reversed()))!"
   }
}
let taylor = Singer(name: "Taylor Swift")
print(taylor.reversedName)

懶單例(Lazy singletons)

單例模式是我不太喜歡的幾種常見(jiàn)編程模式之一实柠。如果你不熟悉它們,那么單例就是一個(gè)值或?qū)ο笊普牵辉O(shè)計(jì)(和編碼)為只創(chuàng)建一次窒盐,并在整個(gè)程序中共享草则。例如,如果你的應(yīng)用程序使用一個(gè)日志程序蟹漓,你可以在應(yīng)用程序運(yùn)行時(shí)創(chuàng)建一個(gè)日志程序?qū)ο罂缓幔⒆屗衅渌a使用該共享實(shí)例。

我不太喜歡單例的原因很簡(jiǎn)單:它們經(jīng)常被用作全局變量葡粒。許多人會(huì)鼓吹全局變量是不好的份殿,然后很高興地以幾乎相同的方式濫用單例,這是草率的嗽交。

話雖如此卿嘲,使用單例的理由也很充分,而且蘋(píng)果有時(shí)也會(huì)使用單例轮纫。如果你的對(duì)象只能存在一次——比如一個(gè)UIApplication的實(shí)例——那么單例就有意義了腔寡。在 iOS系統(tǒng)中,像UIDevice這樣的東西作為單例是有意義的掌唾,因?yàn)樗鼈冎荒艽嬖谝淮畏徘啊H绻阆朐谑褂脝卫龝r(shí)添加額外的代碼,單例也很有用(至少與全局變量相比)糯彬。

所以:只要你仔細(xì)考慮它們的使用凭语,單例還是有一席之地的。如果你認(rèn)為單例是完美的選擇撩扒,我有個(gè)好消息:Swift 讓單例變得異常容易似扔。

舉個(gè)實(shí)際的例子,我們將創(chuàng)建一個(gè)Singer類(lèi)搓谆,它將有一個(gè)MusicPlayer類(lèi)作為屬性炒辉。這需要是一個(gè)單例,因?yàn)闊o(wú)論我們的應(yīng)用程序中有多少歌手泉手,我們都希望他們所有的歌曲通過(guò)同一個(gè)音樂(lè)播放器播放黔寇,這樣音樂(lè)就不會(huì)重疊。

下面是MusicPlayer類(lèi):

class MusicPlayer {
   init() {
      print("Ready to play songs!")
   }
}

它只在創(chuàng)建消息時(shí)打印消息斩萌。

下面是基本的Singer類(lèi)缝裤,它在創(chuàng)建消息時(shí)只打印消息:

class Singer {
   init() {
      print("Creating a new singer")
   }
}

現(xiàn)在對(duì)于單例:如果我們想給Singer類(lèi)一個(gè)MusicPlayer 單例屬性,我們只需要在Singer類(lèi)中添加一行代碼:

static let musicPlayer = MusicPlayer()

就是這樣颊郎。static部分意味著這個(gè)屬性由類(lèi)共享憋飞,而不是由類(lèi)的實(shí)例共享,這意味著你使用Singer.musicPlayer而不是taylor.musicPlayer姆吭。let部分當(dāng)然意味著它是一個(gè)常量榛做。

你可能想知道所有這些與惰性屬性有什么關(guān)系,現(xiàn)在是時(shí)候找出答案了——將這段代碼放到一個(gè)playground中:

class MusicPlayer {
   init() {
      print("Ready to play songs!")
   }
}
class Singer {
   static let musicPlayer = MusicPlayer()
   init() {
      print("Creating a new singer")
   }
}
let taylor = Singer()

當(dāng)它運(yùn)行時(shí),輸出是 Creating a new singerReady to play songs! 消息將不會(huì)出現(xiàn)瘤睹。如果你在playground的結(jié)尾再加一行升敲,只有這樣,信息才會(huì)出現(xiàn):

Singer.musicPlayer

是的:所有 Swift static let單例都是自動(dòng)懶加載的——它們只有在需要時(shí)才會(huì)被創(chuàng)建轰传。這很容易做到,但也非常有效瘪撇。謝謝, Swift 開(kāi)發(fā)團(tuán)隊(duì)!

懶序列(Lazy sequences)

現(xiàn)在你已經(jīng)了解了惰性屬性获茬,我想簡(jiǎn)要地解釋一下惰性序列的用處。這些類(lèi)似于延遲屬性倔既,因?yàn)樗鼈儗⒐ぷ餮舆t到必要的時(shí)候裁厅,但是它們并不像你稍后將看到的那樣高效扒秸。

讓我們從一個(gè)簡(jiǎn)單的例子開(kāi)始:斐波那契數(shù)列。提醒一下,這是從 01開(kāi)始的數(shù)字序列亲怠,其中后面的每個(gè)數(shù)字都是前兩個(gè)數(shù)字之和。序列是0 1 1 2 3 5 8 13 21 34 55赊抖,以此類(lèi)推全谤。

我們可以編寫(xiě)一個(gè)函數(shù),該函數(shù)計(jì)算特定點(diǎn)的斐波那契數(shù)安皱,如下所示:

func fibonacci(of num: Int) -> Int {
   if num < 2 {
      return num
   } else {
      return fibonacci(of: num - 1) + fibonacci(of: num - 2)
   }
}

這是一個(gè)遞歸函數(shù):它調(diào)用自己调鬓。這是一個(gè) nai?ve (幼稚的) 實(shí)現(xiàn),因?yàn)樗粫?huì)在運(yùn)行時(shí)緩存結(jié)果酌伊,這這意味著fibonacci(of: num - 1)所做的所有添加操作不會(huì)被fibonacci(of: num - 2)重用腾窝,即使它可以被重用。但是居砖,這個(gè)實(shí)現(xiàn)非常適合演示延遲序列的好處(和缺點(diǎn)!)

打開(kāi)一個(gè) Playground虹脯,并添加以下代碼:

func fibonacci(of num: Int) -> Int {
   if num < 2 {
      return num
   } else {
      return fibonacci(of: num - 1) + fibonacci(of: num - 2)
   }
}
let fibonacciSequence = (0...20).map(fibonacci)
print(fibonacciSequence[10])

它計(jì)算斐波那契序列的前 21 個(gè)數(shù)字,并打印出第 11個(gè)數(shù)字:55奏候。我讓你把它放在Playground上循集,因?yàn)?Xcode 會(huì)告訴你代碼執(zhí)行的頻率,你會(huì)看到返回 num 行被調(diào)用 28656 次——這是一個(gè)巨大的工作量鼻由。如果你嘗試使用 0…21 作為范圍-只是一個(gè)數(shù)字的提高暇榴!你會(huì)看到這個(gè)數(shù)字上升到 46367 次。

就像我說(shuō)的蕉世,這是一個(gè) nai?ve (幼稚的) 的實(shí)現(xiàn),它的伸縮性不是很好蔼紧。你能想象用 0…199 嗎?如果你只需要幾個(gè)數(shù)字狠轻,而不是所有的數(shù)字怎么辦奸例?

這就是延遲序列發(fā)揮作用的地方:你給它一個(gè)要處理的序列,并告訴它你想要運(yùn)行什么代碼,就像你對(duì)普通序列所做的一樣查吊,但是現(xiàn)在谐区,當(dāng)你訪問(wèn)項(xiàng)時(shí),代碼是按需執(zhí)行的逻卖。因此宋列,我們可以準(zhǔn)備生成斐波那契數(shù)列的前 200 個(gè)數(shù)字,然后使用序列的惰性屬性只使用第 20 個(gè)值:

let lazyFibonacciSequence = Array(0...199).lazy.map(fibonacci)
print(lazyFibonacciSequence[19])

注意:你需要使用 Array评也,以確保 Swift 在數(shù)組上創(chuàng)建延遲映射炼杖,而不是在 0…199 范圍內(nèi)創(chuàng)建延遲映射。

新代碼的運(yùn)行需要少量的時(shí)間盗迟,因?yàn)樗衅渌?jì)算都不會(huì)運(yùn)行—不會(huì)浪費(fèi)時(shí)間坤邪。

然而,盡管延遲序列很聰明罚缕,但是它們有一個(gè)延遲屬性所沒(méi)有的缺點(diǎn):它們沒(méi)有記憶艇纺。這是一種常見(jiàn)的優(yōu)化技術(shù),它存儲(chǔ)計(jì)算代價(jià)高昂的代碼的結(jié)果邮弹,因此不需要再次創(chuàng)建它黔衡。這本質(zhì)上是常規(guī)惰性變量提供給我們的:它不僅保證了一個(gè)屬性在不使用的情況下不會(huì)被創(chuàng)建,而且保證了它在一次又一次使用時(shí)不會(huì)被重復(fù)創(chuàng)建肠鲫。

正如我所說(shuō)员帮,延遲序列沒(méi)有記憶,這意味著兩次請(qǐng)求相同的數(shù)據(jù)將需要做兩次工作导饲。試試這個(gè):

let lazyFibonacciSequence = Array(0...199).lazy.map(fibonacci)
print(lazyFibonacciSequence[19])
print(lazyFibonacciSequence[19])
print(lazyFibonacciSequence[19])

你將看到代碼現(xiàn)在運(yùn)行所需的時(shí)間是以前的三倍捞高。因此,在必要時(shí)使用惰性序列渣锦,但是請(qǐng)記住硝岗,在某些情況下,它們實(shí)際上可能會(huì)減慢你的速度!

析構(gòu)(Destructuring)

析構(gòu)(也稱(chēng)為分解)是將數(shù)據(jù)從元組傳輸?shù)皆M和從元組傳輸?shù)皆M的一種聰明的方法袋毙,當(dāng)你開(kāi)始理解它時(shí)型檀,你將認(rèn)識(shí)到析構(gòu)和模式匹配是如何緊密地聯(lián)系在一起的。析構(gòu)有三種用途:將元組分成多個(gè)值听盖,同時(shí)分配給多個(gè)對(duì)象胀溺,以及交換值

考慮一下這個(gè)元組:

let data = ("one", "two", "three")

如果你想從這三個(gè)值中創(chuàng)建三個(gè)不同的常量皆看,而不進(jìn)行析構(gòu)仓坞,你需要這樣寫(xiě):

let one = data.0
let two = data.1
let three = data.2

通過(guò)析構(gòu),你可以這樣寫(xiě):

let (one, two, three) = data

Swiftdata元組分成三個(gè)單獨(dú)的常量腰吟,所有這些都在一行代碼中无埃。

當(dāng)你處理返回元組的函數(shù)時(shí),這種技術(shù)尤其有用,當(dāng)你希望返回多個(gè)值時(shí)嫉称,通常使用這種方法侦镇。通常需要分割這些返回值,以便你可以根據(jù) terms(條款)引用它們织阅,尤其是在元組中沒(méi)有名稱(chēng)的情況下壳繁。例如:

func getPerson() -> (String, Int) {
   return ("Taylor Swift", 26)
}
let (name, age) = getPerson()
print("\(name) is \(age) years old")

如果你想在析構(gòu)過(guò)程中忽略值,請(qǐng)使用_荔棉,如下所示:

let (_, age) = getPerson()
print("That person is \(age) years old")

你可以使用相同的技術(shù)同時(shí)分配多個(gè)內(nèi)容氮趋,使用固定值或使用函數(shù)調(diào)用。例如:

let (captain, chef) = ("Janeway", "Neelix")
let (engineer, pilot) = (getEngineer(), getPilot())

這在處理密切相關(guān)的值時(shí)尤其有用江耀,比如矩形的坐標(biāo),并且可以幫助提高可讀性诉植。

最后祥国,元組析構(gòu)適合于交換值。現(xiàn)在晾腔,我要誠(chéng)實(shí)地說(shuō):這種技巧在面試之外很少有用舌稀,即使在面試中,它也是一個(gè)相當(dāng)糟糕的選擇灼擂。然而壁查,我想向你們展示它,因?yàn)槲艺J(rèn)為它展示了 Swift 是多么優(yōu)雅剔应。

那么睡腿,這里開(kāi)始:給定兩個(gè)整數(shù) AB ,如何在不使用第三個(gè)變量的情況下交換它們? 想一想峻贮,甚至可以在 Playground 上嘗試一些代碼席怪。下面是用大多數(shù)語(yǔ)言解決這個(gè)問(wèn)題的方法:

var a = 10
var b = 20
a=a+b
b=a-b 
a=a-b
print(a)
print(b)

在Swift中,多虧了析構(gòu)纤控,你可以把它寫(xiě)在一行:

(b, a) = (a, b)

我認(rèn)為她優(yōu)雅挂捻、高效,而且相當(dāng)漂亮船万。如果你在面試中被問(wèn)到這個(gè)問(wèn)題刻撒,你應(yīng)該能夠把它答好!

帶標(biāo)簽的語(yǔ)句(Labeled statements)

標(biāo)簽已經(jīng)使用了很長(zhǎng)時(shí)間,但當(dāng)開(kāi)發(fā)人員開(kāi)始對(duì)goto不滿時(shí)耿导,它們?cè)诤艽蟪潭壬暇筒皇軞g迎了声怔。Swift 把它們帶回來(lái),但沒(méi)有goto:相反碎节,它們與循環(huán)一起使用捧搞,讓你更容易退出它們。

這里有一些代碼可以創(chuàng)建一個(gè)字符串網(wǎng)格,并標(biāo)記其中一個(gè)帶有 x 的正方形胎撇,其中有一些寶藏 - 這是一個(gè)硬編碼的位置介粘,但在真實(shí)的游戲中,你顯然會(huì)將其隨機(jī)化晚树。然后代碼有兩個(gè)循環(huán)試圖找到寶藏姻采,一個(gè)循環(huán)嵌套在另一個(gè)循環(huán)中:循環(huán)遍歷板中的所有行,然后循環(huán)遍歷每一行中的每一列爵憎。

這是代碼:

var board = [[String]](repeating: [String](repeating: "",count: 10), count: 5)
board[3][5] = "x"
for (rowIndex, cols) in board.enumerated() {
   for (colIndex, col) in cols.enumerated() {
      if col == "x" {
         print("Found the treasure at row \(rowIndex) col \(colIndex)!")
      }
  } 
}

考慮到寶藏可以出現(xiàn)在面板上出現(xiàn)一次慨亲,這段代碼是相當(dāng)浪費(fèi)的:即使在搜索的早期就發(fā)現(xiàn)了寶藏,它也會(huì)繼續(xù)查找宝鼓。如果你認(rèn)為是時(shí)候部署 break了刑棵,那么你是對(duì)的,至少在一定程度上是對(duì)的愚铡。它可能是這樣的:

for (rowIndex, cols) in board.enumerated() {
   for (colIndex, col) in cols.enumerated() {
      if col == "x" {
        print("Found the treasure at row \(rowIndex) col \(colIndex)!")
         break
     }
  }
}

但是蛉签,break只退出一個(gè)循環(huán)級(jí)別,因此它將退出for(colIndex,colo)循環(huán)沥寥,然后繼續(xù)運(yùn)行for(rowIndex碍舍,cols)循環(huán)。是的邑雅,它浪費(fèi)的時(shí)間更少片橡,但它仍在浪費(fèi)一些。你可以添加一個(gè)布爾變量淮野,在找到寶藏時(shí)將其設(shè)置為true捧书,然后你可以使用它來(lái)打破外部循環(huán),但 Swift 有一個(gè)更好的解決方案:帶標(biāo)簽的語(yǔ)句录煤。

帶標(biāo)簽的語(yǔ)句允許你為任何循環(huán)命名鳄厌,這允許你在使用breakcontinue時(shí)引用特定循環(huán)。要?jiǎng)?chuàng)建標(biāo)簽妈踊,只需在任何循環(huán)之前寫(xiě)一個(gè)名稱(chēng)然后寫(xiě)一個(gè)冒號(hào)了嚎。然后,你可以使用break yourLabelNamecontinue yourLabelName直接引用它廊营。

因此歪泳,編寫(xiě)該代碼的最不浪費(fèi)的方式是這樣的:

var board = [[String]](repeating: [String](repeating: "",count: 10), count: 5)
board[5][3] = "x"
rowLoop: for (rowIndex, cols) in board.enumerated() {
   for (colIndex, col) in cols.enumerated() {
      if col == "x" {
         print("Found the treasure at row \(rowIndex) col \(colIndex)!")
         break rowLoop
     }
  }
}

它會(huì)立即跳出兩個(gè)循環(huán),并在for(rowIndex, cols)循環(huán)結(jié)束后繼續(xù)執(zhí)行——非常好露筒。

標(biāo)記循環(huán)是聰明的呐伞,但是 Swift 更進(jìn)一步:它允許你標(biāo)記 if 語(yǔ)句,然后像標(biāo)記循環(huán)一樣從中中斷伶氢。并且想要立即擺脫困境時(shí)趟径,這是非常有用的,如果沒(méi)有這些條件癣防,你可能會(huì)得到一個(gè)由越來(lái)越多的縮進(jìn)條件組成的金字塔蜗巧。

下面是一個(gè)很好的例子,你可以看到它的實(shí)際應(yīng)用:

if userRequestedPrint() {
   if documentSaved() {
      if userAuthenticated() {
         if connectToNetwork() {
            if uploadDocument("resignation.doc") {
               if printDocument() {
                  print("Printed successfully!")
               }
            }
         }
      }
   }
}

這段代碼要經(jīng)過(guò)一系列檢查才能允許用戶打印文檔:不要嘗試運(yùn)行它蕾盯,因?yàn)槟切┖瘮?shù)不是真的!

如果所有條件都為true幕屹,那么你將看到打印成功!

標(biāo)記語(yǔ)句允許你為if語(yǔ)句創(chuàng)建早期返回。它們正常運(yùn)行级遭,但在你認(rèn)為有必要的任何時(shí)候望拖,你都可以退出任何條件語(yǔ)句。例如挫鸽,我們可以將上面的金字塔重寫(xiě)為:

printing: if userRequestedPrint() {
   if !documentSaved() { break printing }
   if !userAuthenticated() { break printing }
   if !connectToNetwork() { break printing }
   if !uploadDocument("work.doc") { break printing }
   if !printDocument() { break printing }
   print("Printed successfully!")
}

這樣占用的行數(shù)更少说敏,對(duì)讀取代碼的人的縮進(jìn)也更少,而且很快就能找到滿意的解決方案丢郊。

如果你愿意像云,你甚至可以使用guard來(lái)使你的意圖更加清晰,就像這樣:

printing: if userRequestedPrint() {
   guard documentSaved() else { break printing }
   guard userAuthenticated() else { break printing }
   guard connectToNetwork() else { break printing }
   guard uploadDocument("work.doc") else { break printing }
   guard printDocument() else { break printing }
   print("Printed successfully!")
}

為了可讀性蚂夕,我更喜歡測(cè)試正面條件,而不是反面條件腋逆。也就是說(shuō)婿牍,我寧愿測(cè)試if documentsaved()而不是if !documentsaved()是因?yàn)樗菀桌斫猓?code>guard就是這樣做的惩歉。

嵌套函數(shù)等脂、類(lèi)和結(jié)構(gòu)(Nested functions, classes and structs)

Swift 允許你將一種數(shù)據(jù)類(lèi)型嵌套到另一種數(shù)據(jù)類(lèi)型中,例如結(jié)構(gòu)體中的結(jié)構(gòu)體撑蚌,類(lèi)中的枚舉或函數(shù)中的函數(shù)上遥。這是最常用來(lái)幫助你按照邏輯行為在心理上將事物分組在一起的方法,但有時(shí)會(huì)附加訪問(wèn)語(yǔ)義争涌,以防止不正確地使用嵌套數(shù)據(jù)類(lèi)型粉楚。

讓我們首先處理簡(jiǎn)單的情況:使用嵌套類(lèi)型進(jìn)行邏輯分組×恋妫考慮下面的代碼模软,它定義了一個(gè)名為London的枚舉:

enum London {
   static let coordinates = (lat: 51.507222, long: -0.1275)
   enum SubwayLines {
      case bakerloo, central, circle, district, elizabeth, hammersmithCity, jubilee, metropolitan, northern, piccadilly, victoria, waterlooCity
   }
   enum Places {
      case buckinghamPalace, cityHall, oldBailey, piccadilly, stPaulsCathedral
   }
}

該枚舉有一個(gè)稱(chēng)為coordinates(坐標(biāo))的常量,然后是兩個(gè)嵌套枚舉:SubwayLines(地鐵線路)Places(位置)饮潦。但是燃异,值得注意的是,它沒(méi)有自己的case ——它只是被用作其他數(shù)據(jù)的包裝器继蜡。

這樣做有兩個(gè)直接的好處:首先回俐,任何具有代碼完成功能的 IDE 都可以通過(guò)在鍵入時(shí)列出可能的選項(xiàng)(例如London.Places.cityHall)來(lái)快速逛腿、方便地深入到特定的項(xiàng)。其次仅颇,因?yàn)槟銓?shí)際上是在創(chuàng)建名稱(chēng)空間常量单默,所以你可以使用Piccadilly這樣的合理名稱(chēng),而不必?fù)?dān)心你指的是地鐵線路還是那個(gè)地方灵莲,或者你指的是 London Piccadilly 還是 Manchester Piccadilly雕凹。

如果你進(jìn)一步擴(kuò)展此技術(shù),你將認(rèn)識(shí)到你可以將其用于故事板 ID政冻、表視圖單元 ID 枚抵、圖像名稱(chēng),以及更有效地處理在蘋(píng)果平臺(tái)上非常流行的字符形式的資源類(lèi)型明场。例如:

enum R {
   enum Storyboards: String {
      case main, detail, upgrade, share, help
   }
   enum Images: String {
      case welcome, home, about, button
  } 
}

如果你能理解為什么我用 R 來(lái)表示它汽摹,那就更好了。要使該技術(shù)適用于圖像苦锨,只需將圖像命名為與枚舉case相同的名稱(chēng)逼泣,并在末尾加上 .png ,例如 about.png舟舒。

嵌套類(lèi)型也適用于其他數(shù)據(jù)類(lèi)型拉庶,例如,你可以有一個(gè)結(jié)構(gòu)體秃励,其中包含它自己的枚舉:

struct Cat {
   enum Breed {
      case britishShortHair, burmese, persian, ragdoll, russianBlue, scottishFold, siamese
   }
   var name: String
   var breed: Breed
}

你也可以把結(jié)構(gòu)體放在結(jié)構(gòu)體中氏仗,當(dāng)它們一起使用時(shí),例如:

struct Deck {
   struct Card {
      enum Suit {
         case hearts, diamonds, clubs, spades
      }
      var rank: Int
      var suit: Suit
   }
   var cards = [Card]()
}

正如你在最后一個(gè)示例中所看到的夺鲜,你可以根據(jù)需要多次嵌套皆尔,結(jié)構(gòu)體嵌套結(jié)構(gòu)體再嵌套枚舉是完全合法的。

嵌套語(yǔ)義(Nesting with semantics)

邏輯分組的嵌套不會(huì)阻止你引用任何嵌套類(lèi)型币励,但是如果嵌套太多慷蠕,就會(huì)有點(diǎn)麻煩:

let home = R.Images.home
let burmese = Cat.Breed.burmese
let hearts = Deck.Card.Suit.hearts

但是,Swift 允許你為嵌套類(lèi)型分配訪問(wèn)控制修飾符食呻,以控制它們的使用方式流炕。當(dāng)嵌套類(lèi)型被設(shè)計(jì)為專(zhuān)門(mén)在其父級(jí)內(nèi)部工作時(shí),這很有用:如果Card結(jié)構(gòu)體只能由Deck體結(jié)構(gòu)使用仅胞,那么你需要訪問(wèn)控制浪感。

警告:如果屬性使用私有類(lèi)型,則屬性本身必須是私有的饼问。為了演示影兽,讓我們?cè)俅慰匆幌?code>Deck示例:

struct Deck {
   struct Card {
      enum Suit {
         case hearts, diamonds, clubs, spades
      }
      var rank: Int
      var suit: Suit
   }
   var cards = [Card]()
}

如果我們希望Suit枚舉是私有的,以便只有Card實(shí)例可以使用它莱革,我們需要使用private enum Suit峻堰。然而讹开,這具有連鎖效應(yīng),要求Cardsuit屬性也是私有的捐名,否則它將在Suit枚舉不可用的地方進(jìn)入旦万。所以,更新之后的代碼是這樣的:

struct Deck {
   struct Card {
      private enum Suit {
         case hearts, diamonds, clubs, spades
      }
      var rank: Int
      private var suit: Suit
   }
   var cards = [Card]()
}

嵌套函數(shù)(Nested functions)

嵌套函數(shù)是嵌套類(lèi)型訪問(wèn)控制的一個(gè)有趣的小例子镶蹋,因?yàn)槌悄隳碛兄付ǔ伤遥駝t它們將自動(dòng)限制在其封閉函數(shù)中。Swift 將嵌套函數(shù)實(shí)現(xiàn)為命名閉包贺归,這意味著它們將自動(dòng)從其封閉函數(shù)中捕獲值淆两。

為了演示嵌套函數(shù),我將創(chuàng)建一個(gè)函數(shù)拂酣,它使用三種距離計(jì)算技術(shù)之一計(jì)算兩點(diǎn)之間的距離: 歐幾里德(使用畢達(dá)哥拉斯定理)秋冰、歐幾里德平方(使用畢達(dá)哥拉斯定理,但出于性能原因避免調(diào)用sqrt()和曼哈頓距離婶熬。如果你不熟悉這些術(shù)語(yǔ)剑勾,“歐幾里得距離”基本上是在兩點(diǎn)之間畫(huà)一條直線,“曼哈頓距離”使用直線幾何來(lái)計(jì)算兩個(gè)笛卡爾坐標(biāo)的絕對(duì)差赵颅。

首先虽另,我們要使用的類(lèi)型的代碼:

import Foundation

struct Point {
   let x: Double
   let y: Double
}
enum DistanceTechnique {
   case euclidean
   case euclideanSquared
   case manhattan
}

我創(chuàng)建了自己的Point類(lèi),以避免依賴(lài)于CGPointCore Graphics饺谬。我們將創(chuàng)建三個(gè)函數(shù)洲赵,每個(gè)函數(shù)都嵌套在一個(gè)父函數(shù)中。本章的重點(diǎn)不是解釋距離計(jì)算商蕴,所以讓我們快速地把它們排除在外:

func calculateEuclideanDistanceSquared(start: Point, end: Point) -> Double {
   let deltaX = start.x - end.x
   let deltaY = start.y - end.y
   return deltaX * deltaX + deltaY * deltaY
}

func calculateEuclideanDistance(start: Point, end: Point) -> Double {
   return sqrt(calculateEuclideanDistanceSquared(start: start, end: end))
}

func calculateManhattanDistance(start: Point, end: Point) -> Double {
   return abs(start.x - end.x) + abs(start.y - end.y)
}

第一個(gè)函數(shù)calculateEuclideanDistanceSquared()使用畢達(dá)哥拉斯定理(勾股定理)計(jì)算兩點(diǎn)之間的直線距離。如果你上學(xué)已經(jīng)有一段時(shí)間了芝发,這個(gè)函數(shù)認(rèn)為兩點(diǎn)之間的 XY 是三角形的兩條邊绪商,然后計(jì)算出三角形的斜邊就是兩點(diǎn)之間的距離。

第二個(gè)函數(shù)calculateEuclideanDistance()建立在calculateEuclideanDistanceSquared()函數(shù)的基礎(chǔ)上辅鲸,通過(guò)計(jì)算結(jié)果的平方根來(lái)給出真實(shí)距離格郁。如果需要非常頻繁地計(jì)算距離,例如每次用戶的手指移動(dòng)時(shí)独悴,調(diào)用sqrt()可能會(huì)影響性能例书,這就是calculateEuclideanDistanceSquared()函數(shù)存在的原因。

最后刻炒,第三個(gè)函數(shù)是calculateManhattanDistance()决采,它計(jì)算兩個(gè)點(diǎn)的 XY 坐標(biāo)之間的絕對(duì)距離之和,就好像你坐在一輛出租車(chē)上圍繞城市中一個(gè)正方形街區(qū)行駛坟奥。

有了這三個(gè)嵌套函數(shù)树瞭,現(xiàn)在只需根據(jù)所要求的技術(shù)選擇正確的選項(xiàng):

switch technique {
case .euclidean:
   return calculateEuclideanDistance(start: start, end: end)
case .euclideanSquared:
   return calculateEuclideanDistanceSquared(start: start, end: end)
case .manhattan:
   return calculateManhattanDistance(start: start, end: end)
}

就是這樣拇厢!以下是完整的代碼:

import Foundation
struct Point {
   let x: Double
   let y: Double
}
enum DistanceTechnique {
   case euclidean
   case euclideanSquared
   case manhattan
}
func calculateDistance(start: Point, end: Point, technique: DistanceTechnique) -> Double {
   func calculateEuclideanDistanceSquared(start: Point, end: Point) -> Double {
      let deltaX = start.x - end.x
      let deltaY = start.y - end.y
      return deltaX * deltaX + deltaY * deltaY
   }
   func calculateEuclideanDistance(start: Point, end: Point) -> Double {
      return sqrt(calculateEuclideanDistanceSquared(start: start, end: end))
   }
   func calculateManhattanDistance(start: Point, end: Point) -> Double {
      return abs(start.x - end.x) + abs(start.y - end.y)
   }
   switch technique {
   case .euclidean:
      return calculateEuclideanDistance(start: start, end: end)
   case .euclideanSquared:
      return calculateEuclideanDistanceSquared(start: start, end: end)
   case .manhattan:
      return calculateManhattanDistance(start: start, end: end)
   }
}

let distance = calculateDistance(start: Point(x: 10, y: 10), end: Point(x: 100, y: 100), technique: .euclidean)

現(xiàn)在,所有這些代碼都是完全有效的晒喷,但它也比需要的更冗長(zhǎng)孝偎。提醒一下,函數(shù)只是命名閉包凉敲,因此它們從其封閉函數(shù)中捕獲任何值衣盾。

在這個(gè)上下文中,這意味著我們不需要讓這三個(gè)嵌套函數(shù)接受任何參數(shù)爷抓,因?yàn)樗鼈兣c封閉函數(shù)接受的參數(shù)相同——如果我們刪除它們势决,它們就會(huì)被自動(dòng)捕獲。這有助于使我們的意圖更清楚:這些嵌套函數(shù)只是在相同數(shù)據(jù)上操作的不同方式废赞,而不是使用特定的值徽龟。

下面是calculateDistance()函數(shù)重寫(xiě)之后的代碼,這樣它就可以從嵌套函數(shù)中刪除參數(shù)唉地,而是依賴(lài)于捕獲:

func calculateDistance(start: Point, end: Point, technique: DistanceTechnique) -> Double {
   func calculateEuclideanDistanceSquared() -> Double {
      let deltaX = start.x - end.x
      let deltaY = start.y - end.y
      return deltaX * deltaX + deltaY * deltaY
   }
   func calculateEuclideanDistance() -> Double {
      return sqrt(calculateEuclideanDistanceSquared())
   }
   func calculateManhattanDistance() -> Double {
      return abs(start.x - end.x) + abs(start.y - end.y)
   }
   switch technique {
   case .euclidean:
      return calculateEuclideanDistance()
   case .euclideanSquared:
      return calculateEuclideanDistanceSquared()
   case .manhattan:
      return calculateManhattanDistance()
   }
}

返回嵌套函數(shù)(Returning nested functions)

嵌套函數(shù)被自動(dòng)限制在它們的封閉函數(shù)中据悔,除非你另有指定,即如果你返回它們耘沼。記住极颓,函數(shù)在 Swift 中屬于一等公民 (first-class data types),因此你可以使用一個(gè)函數(shù)根據(jù)特定條件返回另一個(gè)函數(shù)群嗤。在我們的例子中菠隆,我們可以將calculatedDistance()函數(shù)轉(zhuǎn)換為createdDistanceAlgorithm(),它只接受一個(gè)技術(shù)參數(shù)狂秘,并根據(jù)請(qǐng)求的技術(shù)返回其三個(gè)嵌套函數(shù)之一骇径。

我知道這是顯而易見(jiàn)的,值得重復(fù)的是者春,當(dāng)你使用這種方法時(shí)破衔,嵌套函數(shù)將不再是私有的——它將作為返回值返回給任何人使用。

下面是重寫(xiě)calculateDistance()的代碼钱烟,使它返回以下三個(gè)函數(shù)之一:

func createDistanceAlgorithm(technique: DistanceTechnique) -> (Point, Point) -> Double {
   func calculateEuclideanDistanceSquared(start: Point, end: Point) -> Double {
      let deltaX = start.x - end.x
      let deltaY = start.y - end.y
      return deltaX * deltaX + deltaY * deltaY
   }
   func calculateEuclideanDistance(start: Point, end: Point) -> Double {
      return sqrt(calculateEuclideanDistanceSquared(start: start, end: end))
   }
   func calculateManhattanDistance(start: Point, end: Point) -> Double {
      return abs(start.x - end.x) + abs(start.y - end.y)
   }
   switch technique {
   case .euclidean:
      return calculateEuclideanDistance
   case .euclideanSquared:
      return calculateEuclideanDistanceSquared
   case .manhattan:
      return calculateManhattanDistance
   }
}

注意晰筛,這三個(gè)函數(shù)現(xiàn)在都需要接受參數(shù),因?yàn)樯院髮⑾襁@樣調(diào)用它們:

let distanceAlgorithm = createDistanceAlgorithm(technique: .euclidean)
let distance = distanceAlgorithm(Point(x: 10, y: 10), Point(x: 100, y: 100))

文檔標(biāo)記(Documentation markup)

Swift 有特殊的語(yǔ)法拴袭,允許你將 Markdown 格式的文本嵌入到源代碼中读第,源代碼由 Xcode 解析并顯示在 Quick Help 系統(tǒng)面板中——在編碼時(shí)按 Alt+Cmd+2 將其顯示在 Xcode 窗口的右側(cè)。使用特殊格式的代碼注釋?zhuān)憧梢杂涗洃?yīng)該傳入哪些參數(shù)拥刻、返回值將包含哪些內(nèi)容怜瞒、可以拋出的任何錯(cuò)誤等等。

此文檔與添加到特定代碼中的常規(guī)內(nèi)聯(lián)注釋不同般哼。這些特殊的注釋放在函數(shù)和類(lèi)之前盼砍,用于在 Quick Help 和代碼提示彈出窗口中顯示信息尘吗,并進(jìn)行了格式化,以便人類(lèi)和 Xcode 都能閱讀它們浇坐。

讓我們先把簡(jiǎn)單的事情解決掉:除非你使用后面介紹的特殊關(guān)鍵字之一睬捶,否則你在 Markdown 注釋中編寫(xiě)的所有內(nèi)容都將在 Quick Help 面板中顯示為描述文本。如果你剛開(kāi)始輸入文本近刘,你所寫(xiě)的內(nèi)容將在代碼提示彈出窗口中作為簡(jiǎn)要描述使用擒贸。Xcode 通常可以在自動(dòng)補(bǔ)全的空間中填入 20-30 個(gè)單詞觉渴,但這對(duì)于實(shí)際使用來(lái)說(shuō)太長(zhǎng)了——目標(biāo)是大約 10 個(gè)單詞的簡(jiǎn)潔描述介劫。

Markdown注釋以/**開(kāi)頭,以*/結(jié)尾案淋,像這樣:

/**
Call this function to grok some globs.
*/
func myGreatFunction() {
    // do stuff
}

在本文中座韵,你可以使用一系列 Markdown 格式,如下所示:

/**
  將文本放在`反引號(hào)`中以標(biāo)記代碼; 在你的鍵盤(pán)上這通常與波浪號(hào)?共享一個(gè)鍵踢京。
  * 你可以用星號(hào)和空格開(kāi)頭來(lái)寫(xiě)項(xiàng)目描述誉碴。
     * 縮進(jìn)星號(hào)來(lái)創(chuàng)建子列表
  1. 你可以以1.開(kāi)始編寫(xiě)編號(hào)列表
  1. 后續(xù)條目也可以以1.開(kāi)始,Xcode會(huì)自動(dòng)重新編號(hào)瓣距。
   如果你想寫(xiě)一個(gè)鏈接黔帕,[把你的文本放在中括號(hào)里](鏈接放在小括號(hào)里)
  # 標(biāo)題以#號(hào)開(kāi)始
  ## 副標(biāo)題以##開(kāi)頭
  ### 子副標(biāo)題以###開(kāi)頭,是你會(huì)遇到的最常見(jiàn)的標(biāo)題樣式
  在文本前后寫(xiě)一個(gè)*星號(hào)*蹈丸,使它成為斜體
  在文本前后寫(xiě)**兩個(gè)星號(hào)**成黄,使它加粗
*/

文檔關(guān)鍵字(Documentation keywords)

除了使用文本來(lái)描述函數(shù)外,Swift 還允許你添加在 Quick Help 窗格中顯示的特殊關(guān)鍵字逻杖。有很多這樣的工具奋岁,但是大多數(shù)都只顯示一個(gè)標(biāo)題和一些文本。我通常推薦六種有用的方法荸百,你可以在幾分鐘內(nèi)學(xué)會(huì)它們闻伶。

首先: Returns關(guān)鍵字允許你指定調(diào)用者在函數(shù)成功運(yùn)行時(shí)期望返回的值。請(qǐng)記住代碼自動(dòng)補(bǔ)全提示已經(jīng)顯示了返回值的數(shù)據(jù)類(lèi)型管搪,所以這個(gè)字段用于描述數(shù)據(jù)的實(shí)際含義——我們知道它是一個(gè)字符串,但是它將如何格式化?

- Returns: A string containing a date formatted as RFC-822

接下來(lái)是Parameter關(guān)鍵字铡买。這允許你指定參數(shù)的名稱(chēng)并描述它包含的內(nèi)容更鲁。同樣,代碼自動(dòng)補(bǔ)全提示會(huì)顯示必須使用的數(shù)據(jù)類(lèi)型奇钞,所以這是你提供一些細(xì)節(jié)的機(jī)會(huì):"The name of a Taylor Swift album"澡为。你可以包含盡可能多的參數(shù)行。

- Parameter album: The name of a Taylor Swift album
- Parameter track: The track number to load

第三個(gè)是Throws關(guān)鍵字景埃,它允許你指定一個(gè)用逗號(hào)分隔的錯(cuò)誤類(lèi)型列表媒至,該函數(shù)可以拋出:

- Throws: LoadError.networkFailed, LoadError.writeFailed

第四個(gè)是Precondition顶别,它應(yīng)該用于在調(diào)用函數(shù)之前描述程序的正確狀態(tài)。如果使用純函數(shù)拒啰,這個(gè)先決條件應(yīng)該只依賴(lài)于傳遞給函數(shù)的參數(shù)驯绎,例如inputArray.count > 0:

- Precondition: inputArray.count > 0

第五是Complexity,它在 Swift 標(biāo)準(zhǔn)庫(kù)中很流行谋旦。在 Quick Help 中剩失,這不是特別格式化的,但是對(duì)于使用代碼的其他人來(lái)說(shuō)册着,這是有用的信息拴孤。這應(yīng)該用大O符號(hào)來(lái)寫(xiě),例如:

- Complexity: O(1)

最后是Authors關(guān)鍵字甲捏,一開(kāi)始聽(tīng)起來(lái)很有用演熟,但我表示懷疑∷径伲可以想象芒粹,這用于將函數(shù)作者的名稱(chēng)寫(xiě)入 Quick Help 面板,當(dāng)你需要確定應(yīng)該向誰(shuí)抱怨或表?yè)P(yáng)他們的工作時(shí)免猾,這將非常有用是辕。但是由于 XcodeAuthors放在ReturnsThrowsParameter之前猎提,添加一個(gè)認(rèn)證信息只會(huì)把重要的字段往下推获三。嘗試一下,看看你是怎么想的锨苏,但是請(qǐng)記住疙教,文檔首先是有用的。

- Authors: Paul Hudson

如果你在文檔關(guān)鍵字之間包含更多的自由格式文本伞租,那么它將在 Quick Help 中被正確地放置贞谓。

第二章:類(lèi)型(Types)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市葵诈,隨后出現(xiàn)的幾起案子裸弦,更是在濱河造成了極大的恐慌,老刑警劉巖作喘,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件理疙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡泞坦,警方通過(guò)查閱死者的電腦和手機(jī)窖贤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人赃梧,你說(shuō)我怎么就攤上這事滤蝠。” “怎么了授嘀?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵物咳,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我粤攒,道長(zhǎng)所森,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任夯接,我火速辦了婚禮焕济,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘盔几。我一直安慰自己晴弃,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布逊拍。 她就那樣靜靜地躺著上鞠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪芯丧。 梳的紋絲不亂的頭發(fā)上芍阎,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音缨恒,去河邊找鬼谴咸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛骗露,可吹牛的內(nèi)容都是我干的岭佳。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼萧锉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼珊随!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起柿隙,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤叶洞,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后禀崖,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體盾剩,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡尸曼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年舶担,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了找田。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡叶雹,死狀恐怖财饥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情折晦,我是刑警寧澤钥星,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站满着,受9級(jí)特大地震影響谦炒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜风喇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一宁改、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧魂莫,春花似錦还蹲、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至倦始,卻和暖如春斗遏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鞋邑。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工诵次, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人炫狱。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓藻懒,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親视译。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嬉荆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345