當(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),Pinterest 的 iOS 工程師
模式匹配
Switch/case
不是一個(gè)新概念:插入一個(gè)值累奈,然后執(zhí)行幾個(gè)操作過(guò)程中的一個(gè)贬派。Swift 對(duì)安全的關(guān)注增加了對(duì)混編的要求,所有可能的情況都要滿足––如果沒(méi)有啟用特定的警告澎媒,你將無(wú)法在 C 中獲得某些信息搞乏,但這是相當(dāng)微不足道的。
Swift 的switch
語(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ù)字被 3 和 5 整除腰埂,則返回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?")
}
由于name
和password
用于輸入常量和本地綁定的常量,這段代碼變得更加容易混淆细燎。但是两曼,它們是不同的東西,這就是為什么 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 Swift 和 Adele 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"
: name
是String?
,但是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)
使用這種方法,returnedName
是String
而不是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è)我想要討論的特性存和,它是guard
和if
之間的一個(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)從 1 到 100,打印出所有能被 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 singer — Ready 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ù)列。提醒一下,這是從 0 和 1開(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
Swift 將data
元組分成三個(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ù) A 和 B ,如何在不使用第三個(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)命名鳄厌,這允許你在使用break
或continue
時(shí)引用特定循環(huán)。要?jiǎng)?chuàng)建標(biāo)簽妈踊,只需在任何循環(huán)之前寫(xiě)一個(gè)名稱(chēng)然后寫(xiě)一個(gè)冒號(hào)了嚎。然后,你可以使用break yourLabelName
或continue 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),要求Card
的suit
屬性也是私有的捐名,否則它將在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)于CGPoint
和 Core 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)之間的 X 和 Y 是三角形的兩條邊绪商,然后計(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)的 X 和 Y 坐標(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í)免猾,這將非常有用是辕。但是由于 Xcode 將Authors
放在Returns
、Throws
和Parameter
之前猎提,添加一個(gè)認(rèn)證信息只會(huì)把重要的字段往下推获三。嘗試一下,看看你是怎么想的锨苏,但是請(qǐng)記住疙教,文檔首先是有用的。
- Authors: Paul Hudson
如果你在文檔關(guān)鍵字之間包含更多的自由格式文本伞租,那么它將在 Quick Help 中被正確地放置贞谓。