Swift 4.0 編程語言(三)

86.復(fù)合 Cases

共享相同代碼塊的多個(gè)switch 分支 分支可以合并, 寫在分支后用逗號(hào)分開。如果任何模式匹配, 這個(gè)分支就被認(rèn)為匹配。如果列表很長可以寫作多行。如下:

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"

第一個(gè)分支匹配英文中五個(gè)小寫元音字母。 類似的, 第二個(gè)分支匹配所有小寫輔音字母。最后, default 分支 匹配其余的字母。

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

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軸上的點(diǎn), (0, let distance) 匹配y軸上點(diǎn)。兩個(gè)形式都綁定了一個(gè)距離值,兩種形式下距離都是整形,這就意味著分支代碼總是可以訪問距離的值。

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

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

1.continue

2.break

3.fallthrough

4.return

5.throw

88.Continue

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

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

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"

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

89.Break

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

Break 在循環(huán)語句

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

Break 在Switch 語句

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

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

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

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中的一個(gè)分支會(huì)賦值給可選類型變量 possibleIntegerValue 一個(gè)合適的整數(shù)值。

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

因?yàn)椴豢赡芰谐鏊械目赡茏址? 一個(gè)默認(rèn)的分支用來處理其他沒匹配的字符。默認(rèn)分支不需要做任何事, 所以它只有一個(gè)break語句。當(dāng)默認(rèn)分支被匹配后, break 語句就結(jié)束switch語句的執(zhí)行, 代碼開始從 if let 語句處繼續(xù)執(zhí)行。

90.Fallthrough

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

如果你想要C語言那種效果的 fallthrough, 你可以選擇在分支后面加上 fallthrough 關(guān)鍵字。 下面的例子使用fallthrough 創(chuàng)建一個(gè)數(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."

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

除非integerToDescribe 的值在已知素?cái)?shù)列表里, 否則第一個(gè)分支就不會(huì)匹配。 因?yàn)闆]有其他分支, integerToDescribe 會(huì)被默認(rèn)的分支匹配。

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

91.標(biāo)簽語句

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

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

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

label name: while condition {

statements

}

下面的例子使用帶著標(biāo)簽的break和continue語句,這次游戲有了一個(gè)額外的規(guī)則: 想要贏,你就要登上方格25 如果擲骰子讓你超過了方格25, 你必須重?cái)S,直到投出能夠登上方格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

這個(gè)版本的游戲使用了一個(gè) while 循環(huán)和一個(gè)switch 語句來實(shí)現(xiàn)游戲的邏輯。 while 循環(huán)有一個(gè)語句標(biāo)簽 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)使用了一個(gè)switch語句來考慮移動(dòng)的結(jié)果和是否允許移動(dòng),而不是立即用的玩家的位置:

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

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

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

備注:如果break 語句不使用 gameLoop 標(biāo)簽, 它只會(huì)跳出switch 語句,而不會(huì)跳出while 語句。使用 gameLoop 標(biāo)簽清楚知道要終止哪個(gè)控制語句。

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


92.盡早退出

guard 語句, 很像 if 語句, 根據(jù)表達(dá)式布爾值執(zhí)行語句。 使用guard 語句要求條件必須為真。和 if 語句不同, guard 語句總有一個(gè)else 字句—如果條件是假 else 會(huì)執(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 語句的大括號(hào)后執(zhí)行。所有變量或者常量使用一個(gè)可選綁定賦值,它們作為條件的一部分。

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

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

93.判斷 API 可用性

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

編譯器使用SDK中的有效性信息來判斷你代碼中的所有API。 如果你使用不可用的API,Swift 會(huì)報(bào)一個(gè)編譯錯(cuò)誤。

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

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

}

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

一般形式中, 可用性條件帶著一列平臺(tái)名和版本號(hào)。平臺(tái)名例如 iOS, macOS, watchOS, 和 tvOS—完整列表, 除了指定大版本號(hào)比如 iOS 8, 你還可以指定小版本號(hào)比如 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í)行特定任務(wù)的代碼塊。你給出一個(gè)函數(shù)名來表明它是做什么的, 需要時(shí)使用函數(shù)名來調(diào)用。

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

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

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

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

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

下面例子里的函數(shù)叫 greet(person:), 因?yàn)樗龅氖虑椤斎胍粋€(gè)人的名字然后返回一個(gè)問候。為了完成這個(gè), 你定義一個(gè)輸入?yún)?shù)—一個(gè)字符串類型 person—然后返回一個(gè)字符串類型, 它包含了對(duì)這個(gè)人問候語。

func greet(person: String) -> String {

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

return greeting

}

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

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

print(greet(person: "Anna"))

// 打印 "Hello, Anna!"

print(greet(person: "Brian"))

// 打印 "Hello, Brian!"

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

print(_:separator:terminator:) 函數(shù)第一個(gè)參數(shù)沒有標(biāo)簽, 其他參數(shù)是可選的,因?yàn)樗麄冇心J(rèn)值。這些函數(shù)語法的變化在下面函數(shù)參數(shù)標(biāo)簽、參數(shù)名和默認(rèn)參數(shù)值中描述。 greet(person:) 函數(shù)體先是定義了一個(gè)新的字符串常量 greeting,然后設(shè)置一個(gè)簡單的問候信息。然后 greeting 作為返回值傳出。函數(shù)結(jié)束執(zhí)行然后返回greeting 的當(dāng)前值。 你可以輸入不同值多次調(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ù)到具有表達(dá)式參數(shù)名和不同參數(shù)選項(xiàng)的復(fù)雜函數(shù)。

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

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

func sayHelloWorld() -> String {

return "hello, world"

}

print(sayHelloWorld())

// 打印 "hello, world"

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

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

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

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

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ù),傳入兩個(gè)參數(shù),一個(gè)是帶有person標(biāo)簽的字符串值,一個(gè)是帶有alreadyGreeted標(biāo)簽的布爾值。用逗號(hào)分開。注意這個(gè)函數(shù)跟上面的 greet(person:) 函數(shù)不同。 盡管兩個(gè)函數(shù)名字一樣, greet(person:alreadyGreeted:) 函數(shù)有兩個(gè)參數(shù)而 greet(person:) 函數(shù)只有一個(gè)參數(shù)。

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

func greet(person: String) {

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

}

greet(person: "Dave")

// 打印 "Hello, Dave!"

因?yàn)椴恍枰祷匾粋€(gè)值, 所以函數(shù)定義沒有返回箭頭 (->) 或者一個(gè)返回值。 > **備注**

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

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

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

備注:返回值可以忽略, 但是如果函數(shù)說要返回值就一直要這么做。帶有返回值的函數(shù)禁止控制流跳出函數(shù)底部,如果沒有返回一個(gè)值的話。如果堅(jiān)持這么做會(huì)導(dǎo)致編譯錯(cuò)誤。

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

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

下面的例子定義了一個(gè)函數(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ù)返回包含兩個(gè)整數(shù)值的元組。這些值用最小和最大標(biāo)簽表明,這個(gè)可以在查詢函數(shù)返回值的時(shí)候通過名字訪問它們。

minMax(array:) 函數(shù)體開始時(shí)設(shè)置兩個(gè)變量currentMin 和 currentMax值為數(shù)組第一項(xiàng)的值。然后函數(shù)開始遍歷剩下的值,然后重復(fù)和 currentMin 和 currentMax 值進(jìn)行大小比較。 最后, 最大值和最小值作為一個(gè)元組返回。

因?yàn)樵M的成員值作為函數(shù)返回值被命名, 所以可以點(diǎn)語法來訪問最大值和最小值:

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í),元組的成員不需要命名, 因?yàn)樗鼈兊拿肿鳛楹瘮?shù)返回類型已經(jīng)被指定了。

96.可選元組返回類型

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

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

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

為了處理這種情況, 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)

}

你可以用一個(gè)可選綁定來判斷, 這個(gè)版本的 minMax(array:) 函數(shù)是返回一個(gè)實(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ù)標(biāo)簽和參數(shù)名

每個(gè)函數(shù)參數(shù)既有參數(shù)標(biāo)簽也有參數(shù)名。參數(shù)標(biāo)簽在調(diào)用函數(shù)時(shí)使用; 函數(shù)中的每個(gè)參數(shù)調(diào)用時(shí)使用它們的標(biāo)簽。 參數(shù)名用于函數(shù)的實(shí)現(xiàn)。 默認(rèn)情況, 參數(shù)使用參數(shù)名作為標(biāo)簽。

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ù)名必須唯一。 盡管多個(gè)參數(shù)可能有相同的標(biāo)簽, 唯一的參數(shù)標(biāo)簽會(huì)讓你的代碼更有可讀性。

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

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

func someFunction(argumentLabel parameterName: Int) {

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

// for that parameter.

}

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

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ù)標(biāo)簽的使用允許函數(shù)用表達(dá)方式調(diào)用, 像句子一樣。

省略參數(shù)標(biāo)簽

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

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)

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

默認(rèn)參數(shù)值

你可以給函數(shù)參數(shù)賦一個(gè)默認(rèn)值,寫在類型的后面。如果定義了默認(rèn)值, 調(diào)用函數(shù)時(shí)你就可以忽略這個(gè)參數(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

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

可變參數(shù)

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

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

下面的例子計(jì)算任意長度的數(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

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

輸入輸出參數(shù)

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

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

你可以只傳入一個(gè)變量作為輸入輸出參數(shù)。 你不能傳入一個(gè)常量或者字面值作為參數(shù), 因?yàn)槌A亢妥置媪坎荒芨淖儭.?dāng)你使用它作為輸入輸出參數(shù)時(shí),你可以直接在變量名前加上 (&), 來表示它可以被函數(shù)改變。

備注:輸入輸出參數(shù)不能有默認(rèn)值, 可變參數(shù)不能標(biāo)記為輸入輸出的。

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

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

let temporaryA = a

a = b

b = temporaryA

}

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

你可以調(diào)用 swapTwoInts(::) 函數(shù)交換兩個(gè)整形變量的值。 注意 someInt 和 anotherInt 在傳給函數(shù)的時(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 沒有定義一個(gè)返回值或者返回一個(gè)值。但是它依然改變了 someInt 和 anotherInt 的值。 輸入輸出參數(shù)是函數(shù)影響外部的一種備選方式。

99.函數(shù)類型

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

例如:

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

return a + b

}

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

return a * b

}

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

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

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

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

func printHelloWorld() {

print("hello, world")

}

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

使用函數(shù)類型

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

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

這段代碼可以解讀為:

“定義一個(gè)變量 mathFunction, 帶有兩個(gè)整形值的函數(shù)類型, 然后返回一個(gè)整形值。’ 調(diào)用函數(shù) addTwoInts 給這個(gè)變量設(shè)置值。”

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"

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

let anotherMathFunction = addTwoInts

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

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

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

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

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

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

}

printMathResult(addTwoInts, 3, 5)

// 打印 "Result: 8"

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

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

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

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

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

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

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

return input + 1

?}

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

return input - 1

}

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

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

return backward ? stepBackward : stepForward

}

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

var currentValue = 3

let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)

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

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

moveNearerToZero 指向恰當(dāng)?shù)暮瘮?shù), 它可以用來計(jì)步到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ù)默認(rèn)對(duì)外界隱藏, 但是對(duì)于它們的封閉函數(shù)依然可以使用。封閉函數(shù)可以返回一個(gè)嵌套函數(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 中的相似。

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

備注:如果不熟悉閉包的概念不用擔(dān)心。 下面會(huì)詳細(xì)描述。

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

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

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

閉包表達(dá)式是匿名閉包,使用輕量級(jí)語法書寫,可以捕獲上下文的值。

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

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

從單個(gè)表達(dá)式閉包隱式返回

簡約參數(shù)名

尾部閉包語法


閉包表達(dá)式

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

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

101.排序函數(shù)

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

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

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

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

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

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

閉包表達(dá)式語法

閉包表達(dá)式一般是下面這種形式:

{ (parameters) -> return type in

statements

}

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

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. 不過, 對(duì)于內(nèi)聯(lián)閉包表達(dá)式, 參數(shù)和返回類型都寫作花括號(hào)里,而不是外部。

閉包體以關(guān)鍵字in 開始。這個(gè)關(guān)鍵詞的意思是閉包參數(shù)和返回類型的定義完成了, 閉包體開始了。

因?yàn)殚]包體太短, 它甚至可以寫成一行:

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

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

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

由于排序閉包作為參數(shù)傳給函數(shù), Swift 可以推斷它的參數(shù)類型和返回值類型。sorted(by:) 方法被字符串?dāng)?shù)組調(diào)用, 所以它的參數(shù)類型是一個(gè)函數(shù)類型 (String, String) -> Bool. 這就是說 (String, String) 和 Bool 類型無需寫出來作為閉包表達(dá)式定義的一部分。因?yàn)樗械念愋投伎梢酝茢? 返回箭頭 (->) 和參數(shù)名字外的括號(hào)都可以被忽略:

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

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

盡管如此, 如果你想你依然可以顯式說明類型, 如果可以避免代碼的二義性,這樣做是鼓勵(lì)的。sorted(by:) 方法這種情況, 閉包的目的很清晰,就是排序。讀者假設(shè)這個(gè)閉包可能作用于字符串值是安全的。因?yàn)樗鼛椭址當(dāng)?shù)組的排序。

103.從單一表達(dá)式閉包隱式返回

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

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

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

104.速記參數(shù)名

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

如果你在閉包表達(dá)式使用這些速記參數(shù)名, 你可以忽略閉包定義的參數(shù)列表和個(gè)數(shù),然后速記參數(shù)名類型會(huì)被期望的函數(shù)類型推斷出來。 關(guān)鍵字in 也可以被忽略, 因?yàn)殚]包表達(dá)式由它的整個(gè)包體組成。

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

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

105.運(yùn)算符方法

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

reversedNames = names.sorted(by: >)

106.尾閉包

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

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 }

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

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

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

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

這里展示使用帶尾閉包的 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)建了一個(gè)字典,在整數(shù)字和英文名之間建立迎神。同時(shí)定義了一個(gè)整數(shù)數(shù)組, 準(zhǔn)備轉(zhuǎn)換為字符串?dāng)?shù)組。

現(xiàn)在你可以用這個(gè)數(shù)字?jǐn)?shù)組來創(chuàng)建一個(gè)字符串?dāng)?shù)組, 把閉包表達(dá)式作為尾閉包傳入數(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ù)組每個(gè)元素調(diào)用一次 map(_:) 方法。 你不需要指定閉包輸入?yún)?shù)的類型, number, 因?yàn)檫@個(gè)值可以被推斷出來。

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

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

備注

調(diào)用字典下標(biāo)后面跟著感嘆號(hào) (!), 因?yàn)樽值湎聵?biāo)返回一個(gè)可選值來表示如果鍵不存在查找就會(huì)失敗。上面的例子里, 可以保證每次能找到有效的下標(biāo)鍵, 所以感嘆號(hào)用來強(qiáng)制拆包存在的值。

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

然后數(shù)字變量除以10, 因?yàn)樗且粋€(gè)整數(shù), 因?yàn)槿≌麛?shù)倍, 所以 16 變成 1, 58 變成 5, 510 變成 51.

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

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

107.捕獲值

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

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

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

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

var runningTotal = 0

func incrementer() -> Int {

runningTotal += amount

return runningTotal

?}

return incrementer

}

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

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

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

單獨(dú)拿出來, 內(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é)束后不會(huì)消失, 也確保了 runningTotal 在下一次調(diào)用 incrementer 函數(shù)時(shí)依然有效。

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

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

這里有一個(gè) makeIncrementer 活動(dòng)的例子:

let incrementByTen = makeIncrementer(forIncrement: 10)

這個(gè)例子設(shè)置了一個(gè)常量 incrementByTen,它調(diào)用增量器函數(shù),這個(gè)函數(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)建一個(gè)增量器, 它會(huì)有自己新的單獨(dú)的runningTotal 變量:

let incrementBySeven = makeIncrementer(forIncrement: 7)

incrementBySeven()

// returns a value of 7

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

incrementByTen()

// returns a value of 40

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

閉包是引用類型

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

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

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

let alsoIncrementByTen = incrementByTen

alsoIncrementByTen()

// returns a value of 50

逃逸閉包

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

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

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

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

completionHandlers.append(completionHandler)

}

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

標(biāo)記 @escaping 意思是你必須在閉包里顯式引用self。例如, 下面的代碼, 傳給 someFunctionWithEscapingClosure(:) 的閉包是一個(gè)逃逸閉包, 意思是需要顯式引用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"

自動(dòng)閉包

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

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

自動(dòng)閉包讓你延遲執(zhí)行, 直到你調(diào)用閉包內(nèi)部代碼才會(huì)運(yùn)行。延遲作用對(duì)于邊界影響和昂貴計(jì)算代碼很有用, 因?yàn)樗梢宰尶刂拼a什么時(shí)候執(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ù)組第一個(gè)元素被閉包內(nèi)的代碼給移除了, 但是直到這個(gè)閉包實(shí)際被調(diào)用,數(shù)組的元素才會(huì)被移除。如果閉包永不調(diào)用, 閉包內(nèi)的表達(dá)式永遠(yuǎn)不會(huì)執(zhí)行, 也就是說數(shù)組元素永不會(huì)被移除。注意 customerProvider 類型不是 String 而是 () -> String—不帶參數(shù)的函數(shù)只是返回一個(gè)字符串。

當(dāng)你把閉包傳給函數(shù)時(shí),你會(huì)得到相同的延遲執(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ù)有一個(gè)顯式閉包用戶返回一個(gè)用戶名。 下面版本的 serve(customer:) 做了相同的操作,不過, 不是接受一個(gè)顯式閉包, 而是在參數(shù)類型前用了 @autoclosure 屬性。 現(xiàn)在你可以調(diào)用這個(gè)函數(shù),好像他是帶有一個(gè)String 參數(shù)而不是一個(gè)閉包。這個(gè)參數(shù)會(huì)自動(dòng)轉(zhuǎn)換為閉包,因?yàn)?customerProvider 參數(shù)類型已經(jīng)標(biāo)記為 @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!"

備注

過度使用自動(dòng)閉包會(huì)讓你的代碼很難理解。 上下文和函數(shù)名字應(yīng)該讓人知道執(zhí)行推遲了。

如果你想讓自動(dòng)閉包可以逃逸, 同時(shí)使用 @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ù)組中的閉包會(huì)執(zhí)行。 結(jié)果就是, customerProvider 參數(shù)值必須被允許逃離函數(shù)范圍。

108.枚舉

一個(gè)枚舉定義一組相關(guān)的常見類型的值, 讓你在代碼中用一個(gè)類型安全的方式工作。

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

另外, 枚舉的不同分支可以指定任何類型對(duì)應(yīng)值。很像其他語言的 unions 或者 variants. 你可以定義一個(gè)常見的相關(guān)的分支集合來作為枚舉的一部分。每部分都有相應(yīng)的值對(duì)應(yīng)它們。

Swift 的枚舉是第一等類型。它們采用傳統(tǒng)上只有類才支持的多種特性, 例如計(jì)算屬性來提供關(guān)于枚舉當(dāng)前值的額外信息, 實(shí)例方法來提供有關(guān)枚舉表示值的功能。枚舉還可以用初始化來定義初始化值; 可以擴(kuò)大它們?cè)袑?shí)現(xiàn)的功能; 并且可以遵守協(xié)議去提供標(biāo)準(zhǔn)功能。

枚舉語法

用enum關(guān)鍵字引入枚舉,在大括號(hào)中定義整個(gè)枚舉:

enum SomeEnumeration {

// enumeration definition goes here

}

這里有個(gè)指南者四個(gè)方位的例子:

enum CompassPoint {

case north

case south

case east

case west

}

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

備注

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

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

enum Planet {

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

}

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

var directionToHead = CompassPoint.west

directionToHead 類型在初始化時(shí)推斷類型。只要 directionToHead 聲明為 CompassPoint, 你可以用一個(gè)短的點(diǎn)語法來給它設(shè)置一個(gè)不同的 CompassPoint 值:

directionToHead = .east

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


109.用Switch語句匹配枚舉值

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

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 語句考察枚舉分支時(shí)一定要詳盡。 如果 .west 分支忽略掉, 代碼會(huì)編譯不過, 因?yàn)樗鼪]有完整考察 CompassPoint 所有分支。 要求詳盡保證枚舉分支不會(huì)被疏忽掉。

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

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)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,196評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,688評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,456評(píng)論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,955評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,195評(píng)論 0 287
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,725評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,608評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,802評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,048評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評(píng)論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,424評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,762評(píng)論 2 372

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