Swift 4.0 編程語言(三)

86.復合 Cases

共享相同代碼塊的多個switch 分支 分支可以合并, 寫在分支后用逗號分開旨别。如果任何模式匹配, 這個分支就被認為匹配蚌讼。如果列表很長可以寫作多行隐圾。如下:

let someCharacter: Character = "e"

switch someCharacter {

case "a", "e", "i", "o", "u":

print("\(someCharacter) is a vowel")

case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",

"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":

print("\(someCharacter) is a consonant")

default:

print("\(someCharacter) is not a vowel or a consonant")

}

// 打印 "e is a vowel"

第一個分支匹配英文中五個小寫元音字母橄镜。 類似的, 第二個分支匹配所有小寫輔音字母必指。最后, default 分支 匹配其余的字母括饶。

符合分支可以包含值綁定株茶。 符合分支所有形式必須包括相同的值綁定集合, 每個綁定必須獲取一個相同類型的值。這個確保, 無論復合分支哪個部分匹配, 分支代碼總是訪問一個綁定值,而且這個值總有相同類型图焰。

let stillAnotherPoint = (9, 0)

switch stillAnotherPoint {

case (let distance, 0), (0, let distance):

print("On an axis, \(distance) from the origin")

default:

print("Not on an axis")

}

// 打印 "On an axis, 9 from the origin"

上面的分支有兩種形式: (let distance, 0) 匹配x軸上的點, (0, let distance) 匹配y軸上點启盛。兩個形式都綁定了一個距離值,兩種形式下距離都是整形,這就意味著分支代碼總是可以訪問距離的值。

87.控制轉(zhuǎn)移語句

控制轉(zhuǎn)移語句改變代碼執(zhí)行的順序, 通過轉(zhuǎn)移控制從一塊代碼到另外一塊代碼技羔。Swift 有五個控制轉(zhuǎn)移語句:

1.continue

2.break

3.fallthrough

4.return

5.throw

88.Continue

continue 語句告訴當前循環(huán)停止,然后開始下一次循環(huán)僵闯。它說 “我在當前循環(huán)迭代” 沒有離開當前的循環(huán)。

下面的例子從一個小寫字符串移除所有的元音字母和空格,來創(chuàng)建一個謎語:

let puzzleInput = "great minds think alike"

var puzzleOutput = ""

let charactersToRemove: [Character] = ["a", "e", "i", "o", "u", " "]

for character in puzzleInput.characters {

if charactersToRemove.contains(character) {

continue

} else {

puzzleOutput.append(character)

}

}

print(puzzleOutput)

// 打印 "grtmndsthnklk"

上面的代碼遇見元音或者空格就會調(diào)用 continue 關鍵字, 當前迭代立即結(jié)束然后進入下一次迭代藤滥。

89.Break

break 立即結(jié)束整個控制流語句的執(zhí)行鳖粟。break 語句可以用在一個 switch 語句或者循環(huán)語句,如果你想早點結(jié)束執(zhí)行的話。

Break 在循環(huán)語句

用在循環(huán)語句時, break 立即結(jié)束循環(huán),然后跳出循環(huán)所在的大括號拙绊。

Break 在Switch 語句

用在switch 語句時, break 終止 switch 語句的執(zhí)行,然后跳出switch語句所在的大括號向图。

這種行為可以在switch語句中用來匹配或者忽略一個或者多個分支。 因為 Swift 的 switch 語句是詳盡的而且不允許有空分支, 有時候需要故意匹配和忽略一個分支,為了讓你的意圖明顯标沪。整個分支寫一個break就可以忽略榄攀。當分支匹配時, 分支里的break語句立即終止switch語句的執(zhí)行。

下面的例子轉(zhuǎn)換一個字符值,然后判斷它在四種語言之一是否表示一個數(shù)字, 單個switch 分支覆蓋了多個值金句。

let numberSymbol: Character = "三"? // Chinese symbol for the number 3

var possibleIntegerValue: Int?

switch numberSymbol {

case "1", "?", "一", "?":

possibleIntegerValue = 1

case "2", "?", "二", "?":

possibleIntegerValue = 2

case "3", "?", "三", "?":

possibleIntegerValue = 3

case "4", "?", "四", "?":

possibleIntegerValue = 4

default:

break

}

if let integerValue = possibleIntegerValue {

print("The integer value of \(numberSymbol) is \(integerValue).")

} else {

print("An integer value could not be found for \(numberSymbol).")

}

// 打印 "The integer value of 三 is 3."

上面的例子判斷 numberSymbol 是不是拉丁語, 阿拉伯語, 漢語, 或者泰語中的1到4航攒。如果匹配到, switch中的一個分支會賦值給可選類型變量 possibleIntegerValue 一個合適的整數(shù)值。

在 switch 語句執(zhí)行完成之后, 例子中使用可選綁定去判斷一個值是否找到趴梢。possibleIntegerValue 變量是一個可選類型有一個初始值nil, 如果四個分支之一給possibleIntegerValue 設置了一個實際值,這個可選綁定就成功了漠畜。

因為不可能列出所有的可能字符, 一個默認的分支用來處理其他沒匹配的字符。默認分支不需要做任何事, 所以它只有一個break語句坞靶。當默認分支被匹配后, break 語句就結(jié)束switch語句的執(zhí)行, 代碼開始從 if let 語句處繼續(xù)執(zhí)行憔狞。

90.Fallthrough

Swift 的 Switch 語句不會 fall through 到下一個分支。 相反, 只有第一個匹配到的分支語句執(zhí)行完成,整個switch語句就完成了執(zhí)行彰阴。不同的是, C 語言要求每個分支后面都要加上break 語句,防止 fallthrough. 明顯Swift 更加簡潔和安全瘾敢。

如果你想要C語言那種效果的 fallthrough, 你可以選擇在分支后面加上 fallthrough 關鍵字。 下面的例子使用fallthrough 創(chuàng)建一個數(shù)字的文字描述。

let integerToDescribe = 5

var description = "The number \(integerToDescribe) is"

switch integerToDescribe {

case 2, 3, 5, 7, 11, 13, 17, 19:

description += " a prime number, and also"

fallthrough

default:

description += " an integer."

}

print(description)

// 打印 "The number 5 is a prime number, and also an integer."

上例聲明了一個字符串變量 description ,然后賦了一個初始值簇抵。后面的函數(shù)在switch語句中用了這個值 integerToDescribe. 如果 integerToDescribe 的是列表中的一個素數(shù), 函數(shù)就把一段文本添加到 description 的后面, 去備注說這個數(shù)字是素數(shù)庆杜。然后用了fallthrough 關鍵字進入默認的分支. 默認的分支添加一段額外的文本到 description 之后, 然后switch語句就結(jié)束了。

除非integerToDescribe 的值在已知素數(shù)列表里, 否則第一個分支就不會匹配碟摆。 因為沒有其他分支, integerToDescribe 會被默認的分支匹配晃财。

switch 語句執(zhí)行完后, 使用 print(_:separator:terminator:) 函數(shù)打印description。 這個例子里, 數(shù)字5就是一個素數(shù)典蜕。

91.標簽語句

在 Swift 里, 你可以在循環(huán)和條件語句種內(nèi)嵌循環(huán)和條件語句,來創(chuàng)建更復雜的控制流結(jié)構(gòu)断盛。不過, 循環(huán)和條件語句都可以使用break 語句來提前結(jié)束執(zhí)行。因此, 有時候這很有用,當你決定哪個循環(huán)或者條件語句要用break來終止執(zhí)行愉舔。類似的, 如果你有多個嵌套的循環(huán), 它也是有用的,可以明確哪個循環(huán)語句繼續(xù)有效钢猛。

為了達到這些目的, 你可以用一個語句標簽來標記一個循環(huán)或者條件語句。對于條件語句, 你可以使用帶著break語句的語句標簽來結(jié)束標簽的語句轩缤。對于循環(huán)語句, 你可以用帶著break或者continue的語句標簽來結(jié)束或者繼續(xù)標簽語句的執(zhí)行命迈。

一個標簽語句通過放置一個標簽來指示,標簽放在相同行作為語句的關鍵字。跟著是一個冒號火的。 這里有一個while循環(huán)語法的例子, 對于所有的循環(huán)和switch語句都是相當?shù)囊?guī)則:

label name: while condition {

statements

}

下面的例子使用帶著標簽的break和continue語句,這次游戲有了一個額外的規(guī)則: 想要贏,你就要登上方格25 如果擲骰子讓你超過了方格25, 你必須重擲,直到投出能夠登上方格25的數(shù)字為止壶愤。 游戲的棋盤和原來一樣。


finalSquare, board, square, 和 diceRoll 初始化方式跟以前一樣:

let finalSquare = 25

var board = [Int](repeating: 0, count: finalSquare + 1)

board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02

board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08

var square = 0

var diceRoll = 0

這個版本的游戲使用了一個 while 循環(huán)和一個switch 語句來實現(xiàn)游戲的邏輯卫玖。 while 循環(huán)有一個語句標簽 gameLoop 用來表示它是游戲的主要循環(huán)。

while 循環(huán)的條件是是 while square != finalSquare, 告訴你必須登上方格25踊淳。

gameLoop: while square != finalSquare {

diceRoll += 1

if diceRoll == 7 { diceRoll = 1 }

switch square + diceRoll {

case finalSquare:

// diceRoll will move us to the final square, so the game is over

break gameLoop

case let newSquare where newSquare > finalSquare:

// diceRoll will move us beyond the final square, so roll again

continue gameLoop

default:

// this is a valid move, so find out its effect

square += diceRoll

square += board[square]

?}

}

print("Game over!")

每次循環(huán)開始搖色子假瞬。 循環(huán)使用了一個switch語句來考慮移動的結(jié)果和是否允許移動,而不是立即用的玩家的位置:

如果搖色子使得玩家移動到最后的方格,那么游戲就結(jié)束了。break gameLoop 語句轉(zhuǎn)移控制到整個循環(huán)的外面的第一行代碼, 結(jié)束游戲迂尝。

如果搖色子使得玩家超出了最后的方格, 那么移動無效,玩家需要重新?lián)u色子脱茉。continue gameLoop 語句結(jié)束當前的迭代然后開始下一次迭代。

其他所有情況, 搖色子都是有效的移動垄开。 玩家往前移動 diceRoll 方格, 游戲邏輯檢查所有的蛇與梯子琴许。 循環(huán)結(jié)束, 控制返回條件判斷,決定是否需要再來一次。

備注:如果break 語句不使用 gameLoop 標簽, 它只會跳出switch 語句,而不會跳出while 語句溉躲。使用 gameLoop 標簽清楚知道要終止哪個控制語句榜田。

不是必須用 gameLoop 標簽,當調(diào)用 continue gameLoop 來跳到下一次循環(huán)迭代。游戲里只有一個循環(huán)箭券。continue 語句影響哪個循環(huán)是很清楚的。不過, 使用 gameLoop 標簽也沒有壞處疑枯。這樣做是為了和break 標簽使用一致,并且讓邏輯更加輕易閱讀和理解辩块。


92.盡早退出

guard 語句, 很像 if 語句, 根據(jù)表達式布爾值執(zhí)行語句。 使用guard 語句要求條件必須為真。和 if 語句不同, guard 語句總有一個else 字句—如果條件是假 else 會執(zhí)行废亭。

func greet(person: [String: String]) {

guard let name = person["name"] else {

return

}

print("Hello \(name)!")

guard let location = person["location"] else {

print("I hope the weather is nice near you.")

return

}

print("I hope the weather is nice in \(location).")

}

greet(person: ["name": "John"])

// 打印 "Hello John!"

// 打印 "I hope the weather is nice near you."

greet(person: ["name": "Jane", "location": "Cupertino"])

// 打印 "Hello Jane!"

// 打印 "I hope the weather is nice in Cupertino."

如果 guard 語句的條件滿足, 代碼繼續(xù)在guard 語句的大括號后執(zhí)行国章。所有變量或者常量使用一個可選綁定賦值,它們作為條件的一部分。

如果條件沒有滿足, else 分支會執(zhí)行豆村。這個分支轉(zhuǎn)移控制跳出guard 語句所在的代碼塊液兽。可以使用控制轉(zhuǎn)移語句 return, break, continue, 或者 throw, 或者也可以調(diào)用不返回的函數(shù)或者方法, 比如 fatalError(_:file:line:).

跟 if 語句做同樣的判斷比較,使用 guard 語句為了提高代碼的可讀性你画。它讓你可以寫通常不在else 中執(zhí)行的代碼, 同時讓你保持代碼,來處理違法的要求抵碟。

93.判斷 API 可用性

Swift 支持判斷 API 的有效性, 這樣可以保證你不會在給定設備上使用不可用的API。

編譯器使用SDK中的有效性信息來判斷你代碼中的所有API坏匪。 如果你使用不可用的API,Swift 會報一個編譯錯誤拟逮。

在if或者guard語句中使用一個可用性條件去執(zhí)行一塊代碼, 取決于你用的API是否運行時可用。一旦編譯器驗證API在代碼塊可以用,它就會用這些信息适滓。

if #available(iOS 10, macOS 10.12, *) {

// Use iOS 10 APIs on iOS, and use macOS 10.12 APIs on macOS

} else {

// Fall back to earlier iOS and macOS APIs

}

上面的可用性條件指出, 對于iOS, if 語句只能在 iOS 10 和以后的版本上執(zhí)行; 對于 macOS, 只能用在 macOS 10.12 和以后的版本敦迄。 最后一個參數(shù), *, 是需要的,用來指定其他平臺。if 語句執(zhí)行在你指定的最小部署設備上凭迹。

一般形式中, 可用性條件帶著一列平臺名和版本號罚屋。平臺名例如 iOS, macOS, watchOS, 和 tvOS—完整列表, 除了指定大版本號比如 iOS 8, 你還可以指定小版本號比如 iOS 8.3 和 macOS 10.10.3.

if #available(platform name version, ..., *) {

statements to execute if the APIs are available

} else {

fallback statements to execute if the APIs are unavailable

}

94.函數(shù)

函數(shù)包含執(zhí)行特定任務的代碼塊。你給出一個函數(shù)名來表明它是做什么的, 需要時使用函數(shù)名來調(diào)用嗅绸。

Swift 統(tǒng)一的函數(shù)語法是很靈活的, 可以像C語言一樣沒有參數(shù)名,也可以像objective-C一樣帶有名稱和參數(shù)標簽脾猛。參數(shù)可以提供默認值給簡單的函數(shù)調(diào)用,同時可以作為輸入輸出參數(shù)傳入。一旦函數(shù)調(diào)用就會修改傳入的變量鱼鸠。

Swift 中的函數(shù)都有類型, 由參數(shù)類型和返回類型組成猛拴。你可以像使用其他類型一樣使用這個類型, 這個讓傳遞函數(shù)作為參數(shù)變得容易, 也可以從函數(shù)返回函數(shù)。 函數(shù)可以寫在其他函數(shù)里, 在一個內(nèi)嵌函數(shù)范圍封裝喲有用的功能蚀狰。

定義和調(diào)用函數(shù)

當你定義一個函數(shù)時, 你可以定義一個或者多個命名, 函數(shù)的類型值作為輸入, 也就是參數(shù)愉昆。你還可以定義一個值類型,函數(shù)執(zhí)行完傳回值。也就是返回值麻蹋。

每個函數(shù)都有名字, 用來描述函數(shù)執(zhí)行的任務跛溉。 為了使用一個函數(shù), 你通過名字調(diào)用函數(shù),傳給它輸入值 (參數(shù)) 匹配函數(shù)參數(shù)的類型。函數(shù)參數(shù)按照相同順序提供扮授。

下面例子里的函數(shù)叫 greet(person:), 因為它做的事情—它輸入一個人的名字然后返回一個問候芳室。為了完成這個, 你定義一個輸入?yún)?shù)—一個字符串類型 person—然后返回一個字符串類型, 它包含了對這個人問候語。

func greet(person: String) -> String {

let greeting = "Hello, " + person + "!"

return greeting

}

所有信息都在函數(shù)里, 前綴是func 關鍵字刹勃。函數(shù)的返回類型用返回箭頭-> (連字符跟著一個右方向箭頭), 箭頭后是返回的類型名渤愁。

定義描述了函數(shù)的功能, 希望接收的參數(shù), 執(zhí)行完成返回的值。 定義使得函數(shù)在代碼各處可以清晰明確的調(diào)用:

print(greet(person: "Anna"))

// 打印 "Hello, Anna!"

print(greet(person: "Brian"))

// 打印 "Hello, Brian!"

在person 標簽后出入一個字符串類型,調(diào)用 greet(person:) 函數(shù), 比如 greet(person: "Anna"). 因為這個函數(shù)返回一個字符串類型, greet(person:) 可以被 print(_:separator:terminator:) 函數(shù)調(diào)用去打印返回值深夯。 > **備注**

print(_:separator:terminator:) 函數(shù)第一個參數(shù)沒有標簽, 其他參數(shù)是可選的,因為他們有默認值抖格。這些函數(shù)語法的變化在下面函數(shù)參數(shù)標簽诺苹、參數(shù)名和默認參數(shù)值中描述。 greet(person:) 函數(shù)體先是定義了一個新的字符串常量 greeting,然后設置一個簡單的問候信息雹拄。然后 greeting 作為返回值傳出收奔。函數(shù)結(jié)束執(zhí)行然后返回greeting 的當前值。 你可以輸入不同值多次調(diào)用 greet(person:) 函數(shù)滓玖。上面的例子展示了輸入"Anna" 和 "Brian" 發(fā)生了什么坪哄。函數(shù)返回了定制的問候語。 為了讓函數(shù)體變短, 你可以合并信息創(chuàng)建和返回語句到一行代碼:

func greetAgain(person: String) -> String {

return "Hello again, " + person + "!"

}

print(greetAgain(person: "Anna"))

// 打印 "Hello again, Anna!"

95.函數(shù)參數(shù)和返回值

Swift 中函數(shù)的參數(shù)和返回值非常靈活势篡。 你可以定義任何,從帶有不具名參數(shù)的簡單工具函數(shù)到具有表達式參數(shù)名和不同參數(shù)選項的復雜函數(shù)翩肌。

沒有參數(shù)的函數(shù)

函數(shù)不要求有輸入?yún)?shù)。 這里有個沒有輸入?yún)?shù)的函數(shù), 調(diào)用的時候總是返回相同字符串信息:

func sayHelloWorld() -> String {

return "hello, world"

}

print(sayHelloWorld())

// 打印 "hello, world"

函數(shù)定義是還是需要再函數(shù)名后加括號, 盡管它沒有任何參數(shù)禁悠。函數(shù)調(diào)用的時候函數(shù)名后面還是跟著一堆括號念祭。

多個參數(shù)的函數(shù)

函數(shù)可以有多個輸入?yún)?shù), 寫在函數(shù)的括號里, 用逗號分開。

這個函數(shù)有兩個參數(shù),一個人名和是否他們已經(jīng)被問候過碍侦。然后返回對這個人的問候語:

func greet(person: String, alreadyGreeted: Bool) -> String {

if alreadyGreeted {

return greetAgain(person: person)

} else {

return greet(person: person)

}

}

print(greet(person: "Tim", alreadyGreeted: true))

// 打印 "Hello again, Tim!"

你調(diào)用 greet(person:alreadyGreeted:) 函數(shù),傳入兩個參數(shù),一個是帶有person標簽的字符串值,一個是帶有alreadyGreeted標簽的布爾值粱坤。用逗號分開。注意這個函數(shù)跟上面的 greet(person:) 函數(shù)不同瓷产。 盡管兩個函數(shù)名字一樣, greet(person:alreadyGreeted:) 函數(shù)有兩個參數(shù)而 greet(person:) 函數(shù)只有一個參數(shù)站玄。

沒有返回值的函數(shù)

func greet(person: String) {

print("Hello, \(person)!")

}

greet(person: "Dave")

// 打印 "Hello, Dave!"

因為不需要返回一個值, 所以函數(shù)定義沒有返回箭頭 (->) 或者一個返回值。 > **備注**

嚴格來說, 這個版本的 greet(person:) 函數(shù)依然返回一個值, 盡管返回值沒有定義濒旦。函數(shù)沒有返回值是返回了一個特殊的類型 Void. 就是一個簡單的空元組, 寫作 (). 函數(shù)調(diào)用的時候返回值被忽略:

func printAndCount(string: String) -> Int {

print(string)

return string.characters.count

}

func printWithoutCounting(string: String) {

let _ = printAndCount(string: string)

}

printAndCount(string: "hello, world")

// 打印 "hello, world" and returns a value of 12

printWithoutCounting(string: "hello, world")

// 打印 "hello, world" but does not return a value

第一個函數(shù), printAndCount(string:), 打印一個字符串, 然后返回它的字符數(shù)株旷。 第二個函數(shù), printWithoutCounting(string:), 調(diào)用第一個函數(shù), 不過忽略了它的返回值。一旦第二個函數(shù)被調(diào)用, 依然由第一個函數(shù)打印信息,但是不用返回值尔邓。

備注:返回值可以忽略, 但是如果函數(shù)說要返回值就一直要這么做晾剖。帶有返回值的函數(shù)禁止控制流跳出函數(shù)底部,如果沒有返回一個值的話。如果堅持這么做會導致編譯錯誤铃拇。

帶有多返回值的函數(shù)

你可以用元組做為函數(shù)的返回值,來實現(xiàn)返回多個值钞瀑。

下面的例子定義了一個函數(shù) minMax(array:), 用來茶盅整形數(shù)組里面的最小值和最大值:

func minMax(array: [Int]) -> (min: Int, max: Int) {

var currentMin = array[0]

var currentMax = array[0]

for value in array[1.. currentMax {

currentMax = value

? ? } ?

? ?}

return (currentMin, currentMax)

}

minMax(array:) 函數(shù)返回包含兩個整數(shù)值的元組沈撞。這些值用最小和最大標簽表明,這個可以在查詢函數(shù)返回值的時候通過名字訪問它們慷荔。

minMax(array:) 函數(shù)體開始時設置兩個變量currentMin 和 currentMax值為數(shù)組第一項的值。然后函數(shù)開始遍歷剩下的值,然后重復和 currentMin 和 currentMax 值進行大小比較缠俺。 最后, 最大值和最小值作為一個元組返回显晶。

因為元組的成員值作為函數(shù)返回值被命名, 所以可以點語法來訪問最大值和最小值:

let bounds = minMax(array: [8, -6, 2, 109, 3, 71])

print("min is \(bounds.min) and max is \(bounds.max)")

// 打印 "min is -6 and max is 109"

注意函數(shù)返回元組時,元組的成員不需要命名, 因為它們的名字作為函數(shù)返回類型已經(jīng)被指定了。

96.可選元組返回類型

如果函數(shù)返回的元組類型有可能不存在值,你可以使用一個可選元組返回類型來反應整個元組可能為nil壹士。一個可選元組返回類型括號后面跟著一個問號, 比如 (Int, Int)? 或者 (String, Int, Bool)?.

備注:可選元組類型比如s (Int, Int)? 不同于元組中包含可選類型比如 (Int?, Int?). 有一個可選元組類型, 整個元組都是可選的, 不單單是元組里的值磷雇。

minMax(array:) 函數(shù)返回的元組包含兩個整數(shù)值。 然而, 函數(shù)不會對傳入的數(shù)組做任何安全檢查躏救。 如果數(shù)組包含空數(shù)組, minMax(array:) 函數(shù), 上面定義的, 嘗試訪問 array[0] 會觸發(fā)運行時錯誤唯笙。

為了處理這種情況, minMax(array:) 函數(shù)返回值寫成可選元組返回類型,如果數(shù)組為空則返回nil:

func minMax(array: [Int]) -> (min: Int, max: Int)? {

if array.isEmpty { return nil }

var currentMin = array[0]

var currentMax = array[0]

for value in array[1.. currentMax {

currentMax = value

? ? }

? ?}

return (currentMin, currentMax)

}

你可以用一個可選綁定來判斷, 這個版本的 minMax(array:) 函數(shù)是返回一個實際元組值還是nil:

if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {

print("min is \(bounds.min) and max is \(bounds.max)")

}

// 打印 "min is -6 and max is 109"

97.函數(shù)參數(shù)標簽和參數(shù)名

每個函數(shù)參數(shù)既有參數(shù)標簽也有參數(shù)名螟蒸。參數(shù)標簽在調(diào)用函數(shù)時使用; 函數(shù)中的每個參數(shù)調(diào)用時使用它們的標簽。 參數(shù)名用于函數(shù)的實現(xiàn)崩掘。 默認情況, 參數(shù)使用參數(shù)名作為標簽七嫌。

func someFunction(firstParameterName: Int, secondParameterName: Int) {

// In the function body, firstParameterName and secondParameterName

// refer to the argument values for the first and second parameters.

}

someFunction(firstParameterName: 1, secondParameterName: 2)

所有參數(shù)名必須唯一。 盡管多個參數(shù)可能有相同的標簽, 唯一的參數(shù)標簽會讓你的代碼更有可讀性苞慢。

98.指定參數(shù)標簽

參數(shù)標簽寫在參數(shù)名之前, 用空格分開:

func someFunction(argumentLabel parameterName: Int) {

// In the function body, parameterName refers to the argument value

// for that parameter.

}

這里有一個 greet(person:) 函數(shù)的變種,接受一個人名和家鄉(xiāng)然后返回一個問候:

func greet(person: String, from hometown: String) -> String {

return "Hello \(person)!? Glad you could visit from \(hometown)."

}

print(greet(person: "Bill", from: "Cupertino"))

// 打印 "Hello Bill!? Glad you could visit from Cupertino."

參數(shù)標簽的使用允許函數(shù)用表達方式調(diào)用, 像句子一樣诵原。

省略參數(shù)標簽

如果你不想要參數(shù)標簽, 寫一個下劃線代替顯式的參數(shù)標簽。

func someFunction(_ firstParameterName: Int, secondParameterName: Int) {

// In the function body, firstParameterName and secondParameterName

// refer to the argument values for the first and second parameters.

}

someFunction(1, secondParameterName: 2)

如果一個參數(shù)有參數(shù)標簽, 那么調(diào)用函數(shù)的時候參數(shù)必須帶上標簽挽放。

默認參數(shù)值

你可以給函數(shù)參數(shù)賦一個默認值,寫在類型的后面绍赛。如果定義了默認值, 調(diào)用函數(shù)時你就可以忽略這個參數(shù)。

func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {

// If you omit the second argument when calling this function, then

// the value of parameterWithDefault is 12 inside the function body.

}

someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault is 6

someFunction(parameterWithoutDefault: 4) // parameterWithDefault is 12

沒有默認值的參數(shù)放在函數(shù)參數(shù)列表前, 帶有默認值的放在后面辑畦。沒有默認值的參數(shù)通常更重要—首先寫它們更容易知道相同的函數(shù)被調(diào)用, 不用管默認參數(shù)是否被忽略吗蚌。

可變參數(shù)

可變參數(shù)接受零個或者多個指定類型的值。你用可變參數(shù)指定函數(shù)調(diào)用時可以傳入不同個數(shù)的輸入值航闺⊥什猓可變參數(shù)寫法是在參數(shù)類型名后寫三個點。

傳入可變參數(shù)的值在函數(shù)體中作為對應類型的數(shù)組是可用的潦刃。 例如, 帶有數(shù)字名的可變參數(shù)和 Double… 在函數(shù)中作為常量數(shù)組 [Double]是可用的侮措。

下面的例子計算任意長度的數(shù)字的平均數(shù):

func arithmeticMean(_ numbers: Double...) -> Double {

var total: Double = 0

for number in numbers {

total += number

}

return total / Double(numbers.count)

}

arithmeticMean(1, 2, 3, 4, 5)

// returns 3.0, which is the arithmetic mean of these five numbers

arithmeticMean(3, 8.25, 18.75)

// returns 10.0, which is the arithmetic mean of these three numbers

備注:一個函數(shù)最多有一個可變參數(shù)。

輸入輸出參數(shù)

函數(shù)參數(shù)默認是常量乖杠。 嘗試改變會導致編譯期錯誤分扎。這就意味著你不能錯誤的改變參數(shù)值。如果你想函數(shù)改變參數(shù)值, 想要這些改變持續(xù)到函數(shù)調(diào)用結(jié)束, 你可以定義輸入輸出參數(shù)來代替胧洒。

通過把 in-out 關鍵字寫在參數(shù)類型前面來寫一個輸入輸出參數(shù)畏吓。 輸入輸出參數(shù)有一個傳入函數(shù)的值, 會被函數(shù)修改, 然后傳出函數(shù)取代原有的值。 詳情請參照 In-Out Parameters.

你可以只傳入一個變量作為輸入輸出參數(shù)卫漫。 你不能傳入一個常量或者字面值作為參數(shù), 因為常量和字面量不能改變菲饼。當你使用它作為輸入輸出參數(shù)時,你可以直接在變量名前加上 (&), 來表示它可以被函數(shù)改變。

備注:輸入輸出參數(shù)不能有默認值, 可變參數(shù)不能標記為輸入輸出的列赎。

這里有一個函數(shù) swapTwoInts(::), 有兩個輸入輸出整形參數(shù) a 和 b:

func swapTwoInts(_ a: inout Int, _ b: inout Int) {

let temporaryA = a

a = b

b = temporaryA

}

swapTwoInts(::) 函數(shù)簡單的交換a和b的值,這個函數(shù)通過把a的值存儲到一個臨時的常量temporaryA 來實現(xiàn)交換, 把b的值賦給a, 然后把 temporaryA 的值賦給b.

你可以調(diào)用 swapTwoInts(::) 函數(shù)交換兩個整形變量的值宏悦。 注意 someInt 和 anotherInt 在傳給函數(shù)的時候前面都加上了 &:

var someInt = 3

var anotherInt = 107

swapTwoInts(&someInt, &anotherInt)

print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")

// 打印 "someInt is now 107, and anotherInt is now 3"

上面例子展示 someInt 和 anotherInt 的原始值被 swapTwoInts(::) 函數(shù)改變, 盡管它們定義在函數(shù)之外。

備注:輸入輸出參數(shù)與函數(shù)返回值不一樣包吝。swapTwoInts 沒有定義一個返回值或者返回一個值饼煞。但是它依然改變了 someInt 和 anotherInt 的值。 輸入輸出參數(shù)是函數(shù)影響外部的一種備選方式诗越。

99.函數(shù)類型

每個函數(shù)都有一個指定的函數(shù)類型, 由參數(shù)類型和返回值類型組成砖瞧。

例如:

func addTwoInts(_ a: Int, _ b: Int) -> Int {

return a + b

}

func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {

return a * b

}

這個例子定義了兩個簡單的數(shù)學函數(shù) addTwoInts 和 multiplyTwoInts. 兩個函數(shù)都接受兩個整形值, 然后返回一個整形值, 執(zhí)行合適的數(shù)學運算會得出這個結(jié)果。

兩個函數(shù)的類型都是(Int, Int) -> Int. 可以解讀為:

“一個函數(shù)類型有兩個參數(shù), 兩個都是整數(shù)類型t, 然后返回一個整形值”

這里有另外一個例子, 一個沒有參數(shù)和返回值的函數(shù):

func printHelloWorld() {

print("hello, world")

}

這個函數(shù)類型是 () -> Void, 或者 “一個函數(shù)沒有參數(shù),返回 Void.”

使用函數(shù)類型

使用函數(shù)類型跟Swift中其他類型很像嚷狞。例如, 你可以定義一個函數(shù)的常量或者變量,然后把一個函數(shù)賦值給這個變量:

var mathFunction: (Int, Int) -> Int = addTwoInts

這段代碼可以解讀為:

“定義一個變量 mathFunction, 帶有兩個整形值的函數(shù)類型, 然后返回一個整形值块促∪傺撸’ 調(diào)用函數(shù) addTwoInts 給這個變量設置值〗叽洌”

addTwoInts(::) 函數(shù)和 mathFunction 變量一樣有相同的類型, 所有賦值是允許的持隧。

你現(xiàn)在可以使用 mathFunction 名字調(diào)用賦值函數(shù):

print("Result: \(mathFunction(2, 3))")

// 打印 "Result: 5"

有相同匹配類型的不同函數(shù)也可以賦值給相同的變量, 和非函數(shù)類型一樣:

mathFunction = multiplyTwoInts

print("Result: \(mathFunction(2, 3))")

// 打印 "Result: 6"

其他任何類型, 當你把函數(shù)賦值給一個常量或者變量時,你可以留給 Swift 去推斷函數(shù)類型:

let anotherMathFunction = addTwoInts

// anotherMathFunction is inferred to be of type (Int, Int) -> Int

函數(shù)類型作為參數(shù)類型

你可以使用一個函數(shù)類型比如 (Int, Int) -> Int 作為另外一個函數(shù)的參數(shù)。當函數(shù)調(diào)用的時候,你可以把函數(shù)實現(xiàn)的一部分留給調(diào)用者逃片。

這里有個一個例子打印上面數(shù)學函數(shù)的結(jié)果:

func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {

print("Result: \(mathFunction(a, b))")

}

printMathResult(addTwoInts, 3, 5)

// 打印 "Result: 8"

這個例子定義了一個函數(shù) printMathResult(::_:), 有三個參數(shù)屡拨。第一個參數(shù)是 mathFunction, 類型 (Int, Int) -> Int. 你可以傳遞任何這種類型的函數(shù)作為第一個參數(shù)。 第二個和第三個參數(shù)是 a 和 b, 都是整型, 用來作為數(shù)學函數(shù)的輸入值褥实。

當 printMathResult(:::) 調(diào)用時, 傳入 addTwoInts(:_:) 函數(shù), 和整數(shù) 3 和 5. 調(diào)用提供的函數(shù), 然后打印結(jié)果 8.

printMathResult(:::) 任務就是打印特定類型數(shù)學函數(shù)的結(jié)果呀狼。它不關心函數(shù)的實際實現(xiàn)—它只關心函數(shù)類型是否正確。 這使得 printMathResult(:::) 以一種類型安全的方式放手一些功能給函數(shù)的調(diào)用者损离。

函數(shù)類型和返回值

你可以用一個函數(shù)類型作為另外一個函數(shù)的返回類型哥艇。把一個完整的函數(shù)類型寫在返回箭頭后面就可以了。

下一個例子定義了兩個簡單的函數(shù) stepForward(:) 和 stepBackward(:). stepForward(:) 函數(shù)返回一個比輸入值大一的值, stepBackward(:) 函數(shù)返回一個比輸入值小一的值僻澎。兩個函數(shù)都有一個類型 (Int) -> Int:

func stepForward(_ input: Int) -> Int {

return input + 1

?}

func stepBackward(_ input: Int) -> Int {

return input - 1

}

這里有一個函數(shù) chooseStepFunction(backward:), 返回類型是 (Int) -> Int. chooseStepFunction(backward:) 函數(shù)基于一個布爾參數(shù)backward 來返回 stepForward(:) 函數(shù)或 stepBackward(:) 函數(shù):

func chooseStepFunction(backward: Bool) -> (Int) -> Int {

return backward ? stepBackward : stepForward

}

現(xiàn)在你可以使用 chooseStepFunction(backward:) 去獲取一個函數(shù), 然后往前走或者往后走:

var currentValue = 3

let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)

// moveNearerToZero 調(diào)用 stepBackward() 函數(shù)

前面的例子決定是否需要一個負數(shù)或者正數(shù)步去移動currentValue ,讓它逐漸趨近于0. currentValue 有個初始值3, 意味著 currentValue > 0 返回真, chooseStepFunction(backward:) 會返回 stepBackward(_:) 函數(shù)貌踏。 引用的返回函數(shù)存儲在一個常量 moveNearerToZero.

moveNearerToZero 指向恰當?shù)暮瘮?shù), 它可以用來計步到0:

print("Counting to zero:")

// Counting to zero:

while currentValue != 0 {

print("\(currentValue)... ")

currentValue = moveNearerToZero(currentValue)

}

print("zero!")

// 3...

// 2...

// 1...

// zero!

嵌套函數(shù)

到目前為止,你在本章看見的函數(shù)都是全局的示例, 它們定義在全局范圍。你也可以在其他函數(shù)體內(nèi)定義函數(shù),這就是嵌套函數(shù)窟勃。

嵌套函數(shù)默認對外界隱藏, 但是對于它們的封閉函數(shù)依然可以使用祖乳。封閉函數(shù)可以返回一個嵌套函數(shù), 并允許它給外部使用。

你可以重寫上面的 chooseStepFunction(backward:) 例子來使用和返回嵌套函數(shù):

func chooseStepFunction(backward: Bool) -> (Int) -> Int {

func stepForward(input: Int) -> Int { return input + 1 }

func stepBackward(input: Int) -> Int { return input - 1 }

return backward ? stepBackward : stepForward

}

var currentValue = -4

let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)

// moveNearerToZero now refers to the nested stepForward() function

while currentValue != 0 {

print("\(currentValue)... ")

currentValue = moveNearerToZero(currentValue)

}

print("zero!")

// -4...

// -3...

// -2...

// -1...

// zero!


100.閉包

閉包是自包含的功能塊,可以傳遞和用在代碼中秉氧。Swift 中的閉包和C 和 Objective-C 中的相似眷昆。

閉包可以捕獲和存儲上下文定義的任何常量和變量的引用。這就是關閉了常量和變量汁咏。Swift 替你處理閉包的所有內(nèi)存管理亚斋。

備注:如果不熟悉閉包的概念不用擔心。 下面會詳細描述攘滩。

全局和嵌套函數(shù)實際上是一種特殊的閉包帅刊。閉包通常是下面三種形式之一:

全局函數(shù)是閉包,沒有名字也不捕獲任何值。

嵌套函數(shù)是閉包,有一個名字也可以從封閉函數(shù)中捕獲值漂问。

閉包表達式是匿名閉包,使用輕量級語法書寫,可以捕獲上下文的值赖瞒。

Swift 的閉包表達式有一個清楚清晰的風格, 鼓勵簡潔,整齊的語法優(yōu)化。這些優(yōu)化包括:

從上下文推斷參數(shù)和返回值的類型

從單個表達式閉包隱式返回

簡約參數(shù)名

尾部閉包語法


閉包表達式

嵌套函數(shù), 是一個方便的命名方法,也是在較大函數(shù)中定義自包含代碼塊的方式级解。不過, 有時候?qū)懸粋€更短的版本很有用, 它很像函數(shù)但是沒有完整的聲明和名字冒黑。函數(shù)作為參數(shù)的時候這個尤其有用田绑。

閉包表達式是內(nèi)聯(lián)閉包的一種簡寫勤哗。 閉包表達式提供一些語法優(yōu)化,以更短的樣式書寫閉包,但不失清晰和目的性。下面的閉包實例通過精煉一個排序函數(shù)來展示這些優(yōu)化, 每個表達式功能一樣,只是更簡潔掩驱。

101.排序函數(shù)

Swift 標準庫提供了一個方法 sorted(by:), 對已知類型數(shù)組值進行排序, 基于你提供的排序閉包輸出芒划。一旦排序完成, sorted(by:) 方法就會返回一個跟舊數(shù)組相同類型相同大小的新數(shù)組, 數(shù)組元素按照正確的順序冬竟。原來的數(shù)組并沒有被排序方法改變。

下面的閉包表達式使用 sorted(by:) 方法按照字母表倒序排列一個字符串數(shù)組民逼。這是要排序的原來的數(shù)組:

let names = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

sorted(by:) 方法接受帶有兩個相同類型參數(shù)的閉包,作為數(shù)組的內(nèi)容泵殴。然后返回一個布爾值,來說明排序后第一個值是出現(xiàn)在第二個值的前面還是后面。 這個例子是排序一個字符串值的數(shù)組, 所以排序閉包需要是一個函數(shù)類型 (String, String) -> Bool. 提供排序閉包的一個方式是寫一個正確類型的函數(shù), 然后把它作為參數(shù)傳給 sorted(by:) 方法:

func backward(_ s1: String, _ s2: String) -> Bool {

return s1 > s2

}

var reversedNames = names.sorted(by: backward)

// reversedNames is equal to ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

如果第一個字符串 (s1) 大于第二個字符串 (s2), backward(::) 函數(shù)返回真, 表明在排序后數(shù)組中 s1 應該出現(xiàn)在 s2 前面拼苍。 對于字符串中字符, “大于” 意思是 “字母表中出現(xiàn)較晚”笑诅。 也就說字母 “B” 大于字母 “A”, 字符串”Tom” 大于字符串 “Tim”. 這是提供了一個字母表的倒序, “Barry” 排在 “Alex”前面, 等等。

不過, 這種寫法很冗長,它本質(zhì)上是個單一表達式函數(shù) (a > b). 這個例子里, 寫成內(nèi)聯(lián)排序閉包更可取, 使用閉包表達式語法疮鲫。

閉包表達式語法

閉包表達式一般是下面這種形式:

{ (parameters) -> return type in

statements

}

閉包表達式語法參數(shù)可以是輸入輸出參數(shù), 但是它們不能有默認值吆你。 如果你命名可變參數(shù),那么可變參數(shù)也是可以用的。元組也可以用作參數(shù)和返回類型俊犯。 下面的例子展示了之前 backward(_:_:) 函數(shù)的閉包表達式版本:

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in

return s1 > s2

})

內(nèi)聯(lián)閉包的參數(shù)和返回類型聲明跟backward(::)函數(shù)的聲明一樣妇多。兩種情況下, 它寫作 (s1: String, s2: String) -> Bool. 不過, 對于內(nèi)聯(lián)閉包表達式, 參數(shù)和返回類型都寫作花括號里,而不是外部。

閉包體以關鍵字in 開始燕侠。這個關鍵詞的意思是閉包參數(shù)和返回類型的定義完成了, 閉包體開始了者祖。

因為閉包體太短, 它甚至可以寫成一行:

reversedNames = names.sorted(by: { (s1: String, s2: String) -> Bool in return s1 > s2 } )

這個說明 sorted(by:) 方法調(diào)用還是一樣的。一對括號包括方法的所有參數(shù)绢彤。 只不過,現(xiàn)在參數(shù)在一個內(nèi)聯(lián)閉包里七问。

102.根據(jù)上下文推斷類型

由于排序閉包作為參數(shù)傳給函數(shù), Swift 可以推斷它的參數(shù)類型和返回值類型。sorted(by:) 方法被字符串數(shù)組調(diào)用, 所以它的參數(shù)類型是一個函數(shù)類型 (String, String) -> Bool. 這就是說 (String, String) 和 Bool 類型無需寫出來作為閉包表達式定義的一部分茫舶。因為所有的類型都可以推斷, 返回箭頭 (->) 和參數(shù)名字外的括號都可以被忽略:

reversedNames = names.sorted(by: { s1, s2 in return s1 > s2 } )

當把閉包傳給一個函數(shù)或者方法作為內(nèi)聯(lián)閉包表達式時,總是可能要推斷參數(shù)類型和返回類型烂瘫。結(jié)果是, 一旦這個閉包用作函數(shù)或者方法參數(shù),你就不需要用完整的形式書寫內(nèi)聯(lián)閉包。

盡管如此, 如果你想你依然可以顯式說明類型, 如果可以避免代碼的二義性,這樣做是鼓勵的奇适。sorted(by:) 方法這種情況, 閉包的目的很清晰,就是排序坟比。讀者假設這個閉包可能作用于字符串值是安全的。因為它幫助字符串數(shù)組的排序嚷往。

103.從單一表達式閉包隱式返回

通過忽略聲明的返回關鍵字,單一表達式閉包可以隱式返回它們單一表達式的結(jié)果, 就像前一個例子的版本:

reversedNames = names.sorted(by: { s1, s2 in s1 > s2 } )

這里, sorted(by:) 函數(shù)的方法參數(shù)清晰表明閉包要返回一個布爾值葛账。因為閉包體包含了單一表達式(s1 > s2) ,而且返回值是布爾值, 這里沒有二義性, 而且返回值可以忽略。

104.速記參數(shù)名

Swift 自動提供速記參數(shù)名給內(nèi)聯(lián)閉包, 可以用 $0, $1, $2, 來調(diào)用閉包參數(shù)值

如果你在閉包表達式使用這些速記參數(shù)名, 你可以忽略閉包定義的參數(shù)列表和個數(shù),然后速記參數(shù)名類型會被期望的函數(shù)類型推斷出來皮仁。 關鍵字in 也可以被忽略, 因為閉包表達式由它的整個包體組成籍琳。

reversedNames = names.sorted(by: { $0 > $1 } )

這里, $0 和 $1 調(diào)用閉包的第一個和第二個字符串參數(shù)。

105.運算符方法

這里其實有一個更短的方式去寫上面的閉包贷祈。 Swift 的字符串類型定義了它特有的大于號的實現(xiàn), 是帶有兩個字符串類型參數(shù)的方法,然后返回一個布爾類型趋急。這很符合 the sorted(by:) 方法的類型需要。因此, 你可以簡單的傳入大于號, 然后 Swift 會推斷你想要用它的字符串特有的實現(xiàn):

reversedNames = names.sorted(by: >)

106.尾閉包

如果你需要傳給函數(shù)一個閉包表達式作為最后的參數(shù)并且閉包表達式很長的話, 用尾閉包代替是很有用的势誊。尾閉包寫在函數(shù)調(diào)用的括號后面, 盡管它依然是函數(shù)的一個參數(shù)呜达。 當你使用尾閉包語法時, 作為函數(shù)調(diào)用部分,你不要給閉包寫參數(shù)標簽。

func someFunctionThatTakesAClosure(closure: () -> Void) {

// function body goes here

}

// 不使用尾閉包調(diào)用函數(shù):

someFunctionThatTakesAClosure(closure: {

// closure's body goes here

})

// 使用尾閉包調(diào)用函數(shù):

someFunctionThatTakesAClosure() {

// trailing closure's body goes here

}

字符串排序閉包也可以使用尾閉包:

reversedNames = names.sorted() { $0 > $1 }

如果閉包表達式是作為函數(shù)或者方法的唯一參數(shù),并且你使用這個表達式作為尾閉包的話, 你調(diào)用這個函數(shù)的時候可以不用在函數(shù)名后寫括號:

reversedNames = names.sorted { $0 > $1 }

當閉包太長無法寫成內(nèi)聯(lián)單行的時候, 使用尾閉包是最有用的粟耻。有一個例子, Swift 數(shù)組類型有個方法 map(_:) ,帶有一個閉包表達式作為它的單一參數(shù)查近。數(shù)組中的每一項都會調(diào)用它一次, 為每項返回一個替代映射值 (可能是其他類型)眉踱。映射的性質(zhì)和返回值的類型由閉包決定。

對每個數(shù)組元素應用提供的閉包后, map(_:) 方法返回包含新的映射值的新數(shù)組, 和原有數(shù)組對應值的順序一致霜威。

這里展示使用帶尾閉包的 map(_:) 方法如何把整型值數(shù)組轉(zhuǎn)換為字符串值數(shù)組谈喳。數(shù)組 [16, 58, 510] 用來創(chuàng)建新數(shù)組 [“OneSix”, “FiveEight”, “FiveOneZero”]:

let digitNames = [

0: "Zero", 1: "One", 2: "Two",? 3: "Three", 4: "Four",

5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"

]

let numbers = [16, 58, 510]

上面的代碼創(chuàng)建了一個字典,在整數(shù)字和英文名之間建立迎神。同時定義了一個整數(shù)數(shù)組, 準備轉(zhuǎn)換為字符串數(shù)組戈泼。

現(xiàn)在你可以用這個數(shù)字數(shù)組來創(chuàng)建一個字符串數(shù)組, 把閉包表達式作為尾閉包傳入數(shù)組的 map(_:) 方法即可:

let strings = numbers.map {

(number) -> String in

var number = number

var output = ""

repeat {

output = digitNames[number % 10]! + output

number /= 10

} while number > 0

return output

}

// strings is inferred to be of type [String]

// its value is ["OneSix", "FiveEight", "FiveOneZero"]

數(shù)組每個元素調(diào)用一次 map(_:) 方法婿禽。 你不需要指定閉包輸入?yún)?shù)的類型, number, 因為這個值可以被推斷出來。

這個例子里, 變量 number 用閉包的 number 參數(shù)值來初始化, 所以這個值可以在閉包中修改大猛。 (函數(shù)和閉包的參數(shù)總是常量的) 閉包表達式指定了一個字符串返回值類型, 來說明存儲在映射輸出數(shù)組中的類型谈宛。

每次調(diào)用閉包表達式創(chuàng)建一個字符串 output. 它使用余數(shù)運算符結(jié)算數(shù)字的最后一位 (number % 10), 然后用這個位去字典中查找對應的字符串。這個閉包可以用來創(chuàng)建任何大于0數(shù)字的字符串表示胎署。

備注

調(diào)用字典下標后面跟著感嘆號 (!), 因為字典下標返回一個可選值來表示如果鍵不存在查找就會失敗吆录。上面的例子里, 可以保證每次能找到有效的下標鍵, 所以感嘆號用來強制拆包存在的值。

從字典獲取的字符串會加到 output 前, 有效的建立了倒序的數(shù)字字符串版本琼牧。

然后數(shù)字變量除以10, 因為它是一個整數(shù), 因為取整數(shù)倍, 所以 16 變成 1, 58 變成 5, 510 變成 51.

這個過程直到nubmer等于0, 這時閉包返回輸出字符串, 然后通過 map(_:) 方法添加到數(shù)組恢筝。

上例尾閉包語法的使用,完整封裝了閉包的功能。不要把整個閉包包含在 map(_:) 方法的method 外面括弧里巨坊。

107.捕獲值

閉包可以在其定義的上下文中捕獲常量和變量值贩猎。閉包可以在包體內(nèi)調(diào)用和修改這些常量和變量值, 即使定義常量和變量的原來的范圍不存在了夫偶。

在 Swift 中, 可以捕獲值的最簡單的閉包形式是一個內(nèi)嵌函數(shù), 寫在另外一個函數(shù)體內(nèi)。 內(nèi)嵌函數(shù)可以捕獲它外部函數(shù)的任何參數(shù),而且可以捕獲定義在外部函數(shù)的任何常量和變量。

這里有函數(shù)實例 makeIncrementer, 包含了一個內(nèi)嵌函數(shù) incrementer. 內(nèi)嵌函數(shù)從它的上下文 incrementer() 捕獲兩個值, runningTotal 和 amount. 捕獲這兩個值以后, incrementer 被 makeIncrementer 作為閉包返回, 這個閉包每次調(diào)用時會把 runningTotal 增加 amount.

func makeIncrementer(forIncrement amount: Int) -> () -> Int {

var runningTotal = 0

func incrementer() -> Int {

runningTotal += amount

return runningTotal

?}

return incrementer

}

makeIncrementer 的返回類型是 () -> Int. 意思就是返回一個函數(shù), 而不是一個簡單值牌柄。返回的函數(shù)沒有參數(shù), 每次調(diào)用有一個整型值蒋失。

makeIncrementer(forIncrement:) 函數(shù)定義了一個整型變量 runningTotal, 用來保存incrementer 返回的增加的總步數(shù)正什。這個變量初始化值是 0.

makeIncrementer(forIncrement:) 有一個整型參數(shù)帶有forIncrement 標簽, 和一個變量名 amount. 這個參數(shù)傳給指定 runningTotal 應該增加多少步數(shù)丑慎。makeIncrementer 定義了一個內(nèi)嵌函數(shù)incrementer, 用來執(zhí)行實際的遞增操作。 這個函數(shù)簡單把 amount 加到 runningTotal 上去然后返回結(jié)果究珊。

單獨拿出來, 內(nèi)嵌函數(shù) incrementer() 不太尋常:

func incrementer() -> Int {

runningTotal += amount

return runningTotal

}

incrementer() 函數(shù)沒有任何參數(shù), 然而它從函數(shù)體內(nèi)部調(diào)用runningTotal 和 amount薪者。 它從包圍它的函數(shù)里捕獲 runningTotal 和 amount. 引用捕獲確保 runningTotal 和 amount 在調(diào)用 makeIncrementer 結(jié)束后不會消失, 也確保了 runningTotal 在下一次調(diào)用 incrementer 函數(shù)時依然有效。

備注:作為一個優(yōu)化, 如果閉包修改一個值, 而且閉包創(chuàng)建后這個值沒有改變的話,Swift 可能會不會捕獲和保存這個值的副本剿涮。

Swift 同時處理了變量釋放后的所有的內(nèi)存管理言津。

這里有一個 makeIncrementer 活動的例子:

let incrementByTen = makeIncrementer(forIncrement: 10)

這個例子設置了一個常量 incrementByTen,它調(diào)用增量器函數(shù),這個函數(shù)每次調(diào)用把 runningTotal 變量加10. 多次調(diào)用效果如下:

incrementByTen()

// returns a value of 10

incrementByTen()

// returns a value of 20

incrementByTen()

// returns a value of 30

如果你再創(chuàng)建一個增量器, 它會有自己新的單獨的runningTotal 變量:

let incrementBySeven = makeIncrementer(forIncrement: 7)

incrementBySeven()

// returns a value of 7

調(diào)用原先的增量器(incrementByTen) 繼續(xù)增加它的 runningTotal 變量, 不會影響 incrementBySeven 捕獲的變量:

incrementByTen()

// returns a value of 40

備注:如果你把閉包賦值給一個類實例的屬性, 然后閉包會通過引用這個實例或者它的成員來捕獲這個實例。你將在閉包和實例之間建立一個強引用循環(huán)取试。 Swift 使用捕獲列表來打破這種強引用循環(huán)悬槽。

閉包是引用類型

上面的例子, incrementBySeven 和 incrementByTen 都是常量, 但是這些常量引用的閉包依然可以改變它們已經(jīng)捕獲的 runningTotal 變量。 這是因為函數(shù)和閉包是引用類型瞬浓。

每當你賦值函數(shù)或者閉包給一個常量或者變量, 實際上你是設置常量或者變量去引用函數(shù)或者閉包初婆。上面的例子, incrementByTen 引用的閉包選擇是常量, 而不是閉包本身的內(nèi)容。

這也意味著如果你把一個閉包賦值給兩個不同的常量或者變量,它們引用的是相同的閉包:

let alsoIncrementByTen = incrementByTen

alsoIncrementByTen()

// returns a value of 50

逃逸閉包

閉包被傳給函數(shù)作為參數(shù)的事?lián)f會逃逸, 但是會在函數(shù)返回時調(diào)用。 一旦你定義帶有閉包作為參數(shù)的函數(shù), 你可以在參數(shù)類型前書寫 @escaping 來表示這個閉包允許逃逸烟逊。

閉包逃逸的方式之一是,通過存儲在定義在函數(shù)外部的變量中。作為一個例子, 很多函數(shù)用閉包參數(shù)作為一個完成處理器來異步工作铺根。 任務開始時函數(shù)返回, 但是函數(shù)完成之前閉包不會調(diào)用—這個閉包需要逃逸, 后面再調(diào)用宪躯。例如:

var completionHandlers: [() -> Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {

completionHandlers.append(completionHandler)

}

someFunctionWithEscapingClosure(_:) 函數(shù)接受一個閉包作為參數(shù),并且把它添加到外部定義的數(shù)組。如果你不把這個參數(shù)標記為 @escaping, 你會收到一個編譯錯誤位迂。

標記 @escaping 意思是你必須在閉包里顯式引用self访雪。例如, 下面的代碼, 傳給 someFunctionWithEscapingClosure(:) 的閉包是一個逃逸閉包, 意思是需要顯式引用self。 相反, 傳給 someFunctionWithNonescapingClosure(:) 的閉包是非逃逸的, 意思是可以隱式引用self.

func someFunctionWithNonescapingClosure(closure: () -> Void) {

closure()

? } ?

class SomeClass {

var x = 10

func doSomething() {

someFunctionWithEscapingClosure { self.x = 100 }

someFunctionWithNonescapingClosure { x = 200 }

? }

}

let instance = SomeClass()

instance.doSomething()

print(instance.x)

// 打印 "200"

completionHandlers.first?()

print(instance.x)

// 打印 "100"

自動閉包

自動閉包是自動創(chuàng)建的閉包, 它包含了作為參數(shù)傳入函數(shù)的表達式掂林。它沒有任何參數(shù), 并且被調(diào)用時, 它會返回表達式的值臣缀。 這個語法便利讓你可以忽略函數(shù)參數(shù)的花括號,只要寫一個正常的表達式替代顯式的閉包。

調(diào)用帶有自動閉包的函數(shù)很常見, 但是實現(xiàn)那種函數(shù)卻不常見泻帮。例如, assert(condition:message:file:line:) 函數(shù)為它的 condition 和 message 參數(shù)帶了一個自動閉包; 它的condition 參數(shù)只在調(diào)試模式執(zhí)行, message 參數(shù)則在 condition 是假的時候執(zhí)行精置。

自動閉包讓你延遲執(zhí)行, 直到你調(diào)用閉包內(nèi)部代碼才會運行。延遲作用對于邊界影響和昂貴計算代碼很有用, 因為它可以讓控制代碼什么時候執(zhí)行锣杂。 下面的代碼展示閉包的延遲執(zhí)行脂倦。

var customersInLine = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

print(customersInLine.count)

// 打印 "5"

let customerProvider = { customersInLine.remove(at: 0) }

print(customersInLine.count)

// 打印 "5"

print("Now serving \(customerProvider())!")

// 打印 "Now serving Chris!"

print(customersInLine.count)

// 打印 "4"

盡管 customersInLine 數(shù)組第一個元素被閉包內(nèi)的代碼給移除了, 但是直到這個閉包實際被調(diào)用,數(shù)組的元素才會被移除。如果閉包永不調(diào)用, 閉包內(nèi)的表達式永遠不會執(zhí)行, 也就是說數(shù)組元素永不會被移除元莫。注意 customerProvider 類型不是 String 而是 () -> String—不帶參數(shù)的函數(shù)只是返回一個字符串赖阻。

當你把閉包傳給函數(shù)時,你會得到相同的延遲執(zhí)行效果。

// customersInLine is ["Alex", "Ewa", "Barry", "Daniella"]

func serve(customer customerProvider: () -> String) {

print("Now serving \(customerProvider())!")

}

serve(customer: { customersInLine.remove(at: 0) } )

// 打印 "Now serving Alex!"

serve(customer:) 函數(shù)有一個顯式閉包用戶返回一個用戶名踱蠢。 下面版本的 serve(customer:) 做了相同的操作,不過, 不是接受一個顯式閉包, 而是在參數(shù)類型前用了 @autoclosure 屬性火欧。 現(xiàn)在你可以調(diào)用這個函數(shù),好像他是帶有一個String 參數(shù)而不是一個閉包。這個參數(shù)會自動轉(zhuǎn)換為閉包,因為 customerProvider 參數(shù)類型已經(jīng)標記為 @autoclosure .

// customersInLine is ["Ewa", "Barry", "Daniella"]

func serve(customer customerProvider: @autoclosure () -> String) {

print("Now serving \(customerProvider())!")

}

serve(customer: customersInLine.remove(at: 0))

// 打印 "Now serving Ewa!"

備注

過度使用自動閉包會讓你的代碼很難理解茎截。 上下文和函數(shù)名字應該讓人知道執(zhí)行推遲了苇侵。

如果你想讓自動閉包可以逃逸, 同時使用 @autoclosure 和 @escaping 屬性。

// customersInLine is ["Barry", "Daniella"]

var customerProviders: [() -> String] = []

func collectCustomerProviders(_ customerProvider: @autoclosure @escaping () -> String) {

customerProviders.append(customerProvider)

}

collectCustomerProviders(customersInLine.remove(at: 0))

collectCustomerProviders(customersInLine.remove(at: 0))

print("Collected \(customerProviders.count) closures.")

// 打印 "Collected 2 closures."

for customerProvider in customerProviders {

print("Now serving \(customerProvider())!")

}

// 打印 "Now serving Barry!"

// 打印 "Now serving Daniella!"

上面的代碼, 不是調(diào)用作為 customerProvider 參數(shù)的閉包, collectCustomerProviders(_:) 函數(shù)添加閉包到 customerProviders 數(shù)組企锌。 數(shù)組在函數(shù)外部聲明, 意味著函數(shù)返回后數(shù)組中的閉包會執(zhí)行衅檀。 結(jié)果就是, customerProvider 參數(shù)值必須被允許逃離函數(shù)范圍。

108.枚舉

一個枚舉定義一組相關的常見類型的值, 讓你在代碼中用一個類型安全的方式工作霎俩。

如果你熟悉 C, 你就會知道 C 枚舉會給相關的名字賦值一組整型值哀军。 Swift 中的 枚舉更加靈活, 不需要給每個枚舉分支賦值。 如果提供一個值 ( “原始” 值), 這個值可以是字符串,字符,或者任意一個整型或者浮點型的值打却。

另外, 枚舉的不同分支可以指定任何類型對應值杉适。很像其他語言的 unions 或者 variants. 你可以定義一個常見的相關的分支集合來作為枚舉的一部分。每部分都有相應的值對應它們柳击。

Swift 的枚舉是第一等類型猿推。它們采用傳統(tǒng)上只有類才支持的多種特性, 例如計算屬性來提供關于枚舉當前值的額外信息, 實例方法來提供有關枚舉表示值的功能。枚舉還可以用初始化來定義初始化值; 可以擴大它們原有實現(xiàn)的功能; 并且可以遵守協(xié)議去提供標準功能。

枚舉語法

用enum關鍵字引入枚舉,在大括號中定義整個枚舉:

enum SomeEnumeration {

// enumeration definition goes here

}

這里有個指南者四個方位的例子:

enum CompassPoint {

case north

case south

case east

case west

}

定義在枚舉中的值就是枚舉分支 (比如 north, south, east, and west) 你可以用關鍵字來引入新的枚舉分支.

備注

不像 C 和 Objective-C, Swift 枚舉分支創(chuàng)建時不設一個默認的整數(shù)值蹬叭。上面的 CompassPoint 例子, north, south, east 和 west 不會隱式等于 0, 1, 2 和 3. 相反, 不同的枚舉分支在右方都有值t, 有一個顯式定義的 CompassPoint 類型藕咏。

多個分支可以出現(xiàn)在一行,用逗號分開:

enum Planet {

case mercury, venus, earth, mars, jupiter, saturn, uranus, neptune

}

每個枚舉定義定義了一個全新的類型。 像Swift中的其他類型一樣,它們的名字 (比如 CompassPoint 和 Planet) 應該以大寫字母開始秽五。 用單數(shù)名而不是復數(shù)名:

var directionToHead = CompassPoint.west

directionToHead 類型在初始化時推斷類型孽查。只要 directionToHead 聲明為 CompassPoint, 你可以用一個短的點語法來給它設置一個不同的 CompassPoint 值:

directionToHead = .east

directionToHead 類型已經(jīng)知道了, 所以設置值的時候可以丟掉類型了。使用顯式類型枚舉值時這個會讓代碼高度可讀坦喘。


109.用Switch語句匹配枚舉值

你可以用一個switch語句來匹配單個枚舉值:

directionToHead = .south

switch directionToHead {

case .north:

print("Lots of planets have a north")

case .south:

print("Watch out for penguins")

case .east:

print("Where the sun rises")

case .west:

print("Where the skies are blue")

}

// 打印 "Watch out for penguins"

你可以這樣解讀代碼:

“考察 directionToHead 的值盲再。等于 .north, 打印 “Lots of planets have a north”. 等于 .south, 打印 “Watch out for penguins”.”

…等等。

控制流里描述過, switch 語句考察枚舉分支時一定要詳盡瓣铣。 如果 .west 分支忽略掉, 代碼會編譯不過, 因為它沒有完整考察 CompassPoint 所有分支答朋。 要求詳盡保證枚舉分支不會被疏忽掉。

如果提供所有的分支不合適, 你可以提供一個默認分支來覆蓋沒有顯式指明的分支:

let somePlanet = Planet.earth

switch somePlanet {

case .earth:

print("Mostly harmless")

default:

print("Not a safe place for humans")

}

// 打印 "Mostly harmless"

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末棠笑,一起剝皮案震驚了整個濱河市梦碗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蓖救,老刑警劉巖叉弦,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異藻糖,居然都是意外死亡淹冰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門巨柒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來樱拴,“玉大人,你說我怎么就攤上這事洋满【牵” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵牺勾,是天一觀的道長正罢。 經(jīng)常有香客問我,道長驻民,這世上最難降的妖魔是什么翻具? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮回还,結(jié)果婚禮上裆泳,老公的妹妹穿的比我還像新娘。我一直安慰自己柠硕,他們只是感情好工禾,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般闻葵。 火紅的嫁衣襯著肌膚如雪民泵。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天槽畔,我揣著相機與錄音栈妆,去河邊找鬼。 笑死竟痰,一個胖子當著我的面吹牛签钩,可吹牛的內(nèi)容都是我干的掏呼。 我是一名探鬼主播坏快,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼憎夷!你這毒婦竟也來了莽鸿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤拾给,失蹤者是張志新(化名)和其女友劉穎祥得,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蒋得,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡级及,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了额衙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饮焦。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖窍侧,靈堂內(nèi)的尸體忽然破棺而出县踢,到底是詐尸還是另有隱情,我是刑警寧澤伟件,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布硼啤,位于F島的核電站,受9級特大地震影響斧账,放射性物質(zhì)發(fā)生泄漏谴返。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一咧织、第九天 我趴在偏房一處隱蔽的房頂上張望亏镰。 院中可真熱鬧,春花似錦拯爽、人聲如沸索抓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逼肯。三九已至耸黑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間篮幢,已是汗流浹背大刊。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留三椿,地道東北人缺菌。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像搜锰,于是被迫代替她去往敵國和親伴郁。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容