這里的教程為Swift官方教程中文版。

基礎(chǔ)部分

Swift 是一門開發(fā) iOS, macOS, watchOS 和 tvOS 應(yīng)用的新語言。然而,如果你有 C 或者 Objective-C 開發(fā)經(jīng)驗的話,你會發(fā)現(xiàn) Swift 的很多內(nèi)容都是你熟悉的。

Swift 包含了 C 和 Objective-C 上所有基礎(chǔ)數(shù)據(jù)類型,Int 表示整型值; DoubleFloat 表示浮點型值; Bool 是布爾型值;String 是文本型數(shù)據(jù)。 Swift 還提供了三個基本的集合類型,Array、SetDictionary ,詳見 集合類型。

就像 C 語言一樣,Swift 使用變量來進行存儲并通過變量名來關(guān)聯(lián)值。在 Swift 中,廣泛的使用著值不可變的變量,它們就是常量,而且比 C 語言的常量更強大。在 Swift 中,如果你要處理的值不需要改變,那使用常量可以讓你的代碼更加安全并且更清晰地表達你的意圖。

除了我們熟悉的類型,Swift 還增加了 Objective-C 中沒有的高階數(shù)據(jù)類型比如元組(Tuple)。元組可以讓你創(chuàng)建或者傳遞一組數(shù)據(jù),比如作為函數(shù)的返回值時,你可以用一個元組可以返回多個值。

Swift 還增加了可選(Optional)類型,用于處理值缺失的情況??蛇x表示 “那兒有一個值,并且它等于 x ” 或者 “那兒沒有值” 。可選有點像在 Objective-C 中使用 nil ,但是它可以用在任何類型上,不僅僅是類??蛇x類型比 Objective-C 中的 nil 指針更加安全也更具表現(xiàn)力,它是 Swift 許多強大特性的重要組成部分。

Swift 是一門類型安全的語言,這意味著 Swift 可以讓你清楚地知道值的類型。如果你的代碼需要一個 String ,類型安全會阻止你不小心傳入一個 Int 。同樣的,如果你的代碼需要一個 String,類型安全會阻止你意外傳入一個可選的 String 。類型安全可以幫助你在開發(fā)階段盡早發(fā)現(xiàn)并修正錯誤。

常量和變量

常量和變量把一個名字(比如 maximumNumberOfLoginAttempts 或者 welcomeMessage )和一個指定類型的值(比如數(shù)字 10 或者字符串 "Hello" )關(guān)聯(lián)起來。常量的值一旦設(shè)定就不能改變,而變量的值可以隨意更改。

聲明常量和變量

常量和變量必須在使用前聲明,用 let 來聲明常量,用 var 來聲明變量。下面的例子展示了如何用常量和變量來記錄用戶嘗試登錄的次數(shù):

let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0

這兩行代碼可以被理解為:

“聲明一個名字是 maximumNumberOfLoginAttempts 的新常量,并給它一個值 10 。然后,聲明一個名字是 currentLoginAttempt 的變量并將它的值初始化為 0 ?!?/p>

在這個例子中,允許的最大嘗試登錄次數(shù)被聲明為一個常量,因為這個值不會改變。當(dāng)前嘗試登錄次數(shù)被聲明為一個變量,因為每次嘗試登錄失敗的時候都需要增加這個值。

你可以在一行中聲明多個常量或者多個變量,用逗號隔開:

var x = 0.0, y = 0.0, z = 0.0

注意

如果你的代碼中有不需要改變的值,請使用 let 關(guān)鍵字將它聲明為常量。只將需要改變的值聲明為變量。

類型注解

當(dāng)你聲明常量或者變量的時候可以加上類型注解(type annotation),說明常量或者變量中要存儲的值的類型。如果要添加類型注解,需要在常量或者變量名后面加上一個冒號和空格,然后加上類型名稱。

這個例子給 welcomeMessage 變量添加了類型注解,表示這個變量可以存儲 String 類型的值:

var welcomeMessage: String

聲明中的冒號代表著“是...類型”,所以這行代碼可以被理解為:

“聲明一個類型為 String ,名字為 welcomeMessage 的變量。”

“類型為 String ”的意思是“可以存儲任意 String 類型的值。”

welcomeMessage 變量現(xiàn)在可以被設(shè)置成任意字符串:

welcomeMessage = "Hello"

你可以在一行中定義多個同樣類型的變量,用逗號分割,并在最后一個變量名之后添加類型注解:

var red, green, blue: Double

注意

一般來說你很少需要寫類型注解。如果你在聲明常量或者變量的時候賦了一個初始值,Swift 可以推斷出這個常量或者變量的類型,請參考 類型安全和類型推斷。在上面的例子中,沒有給 welcomeMessage 賦初始值,所以變量 welcomeMessage 的類型是通過一個類型注解指定的,而不是通過初始值推斷的。

常量和變量的命名

常量和變量名可以包含任何字符,包括 Unicode 字符:

let π = 3.14159
let 你好 = "你好世界"
let ???? = "dogcow"

常量與變量名不能包含數(shù)學(xué)符號,箭頭,保留的(或者非法的)Unicode 碼位,連線與制表符。也不能以數(shù)字開頭,但是可以在常量與變量名的其他地方包含數(shù)字。

一旦你將常量或者變量聲明為確定的類型,你就不能使用相同的名字再次進行聲明,或者改變其存儲的值的類型。同時,你也不能將常量與變量進行互轉(zhuǎn)。

注意

如果你需要使用與 Swift 保留關(guān)鍵字相同的名稱作為常量或者變量名,你可以使用反引號(`)將關(guān)鍵字包圍的方式將其作為名字使用。無論如何,你應(yīng)當(dāng)避免使用關(guān)鍵字作為常量或變量名,除非你別無選擇。

你可以更改現(xiàn)有的變量值為其他同類型的值,在下面的例子中,friendlyWelcome 的值從 "Hello!" 改為了 "Bonjour!":

var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"
// friendlyWelcome 現(xiàn)在是 "Bonjour!"

與變量不同,常量的值一旦被確定就不能更改了。嘗試這樣做會導(dǎo)致編譯時報錯:

let languageName = "Swift"
languageName = "Swift++"
// 這會報編譯時錯誤 - languageName 不可改變

輸出常量和變量

你可以用 print(_:separator:terminator:) 函數(shù)來輸出當(dāng)前常量或變量的值:

print(friendlyWelcome)
// 輸出“Bonjour!”

print(_:separator:terminator:) 是一個用來輸出一個或多個值到適當(dāng)輸出區(qū)的全局函數(shù)。如果你用 Xcode,print(_:separator:terminator:) 將會輸出內(nèi)容到“console”面板上。separatorterminator 參數(shù)具有默認值,因此你調(diào)用這個函數(shù)的時候可以忽略它們。默認情況下,該函數(shù)通過添加換行符來結(jié)束當(dāng)前行。如果不想換行,可以傳遞一個空字符串給 terminator 參數(shù)--例如,print(someValue, terminator:"") 。關(guān)于參數(shù)默認值的更多信息,請參考 默認參數(shù)值。

Swift 用字符串插值(string interpolation)的方式把常量名或者變量名當(dāng)做占位符加入到長字符串中,Swift 會用當(dāng)前常量或變量的值替換這些占位符。將常量或變量名放入圓括號中,并在開括號前使用反斜杠將其轉(zhuǎn)義:

print("The current value of friendlyWelcome is \(friendlyWelcome)")
// 輸出“The current value of friendlyWelcome is Bonjour!”

注意

字符串插值所有可用的選項,請參考 字符串插值。

注釋

請將你的代碼中的非執(zhí)行文本注釋成提示或者筆記以方便你將來閱讀。Swift 的編譯器將會在編譯代碼時自動忽略掉注釋部分。

Swift 中的注釋與 C 語言的注釋非常相似。單行注釋以雙正斜杠(//)作為起始標記:

// 這是一個注釋

你也可以進行多行注釋,其起始標記為單個正斜杠后跟隨一個星號(/*),終止標記為一個星號后跟隨單個正斜杠(*/):

/* 這也是一個注釋,
但是是多行的 */

與 C 語言多行注釋不同,Swift 的多行注釋可以嵌套在其它的多行注釋之中。你可以先生成一個多行注釋塊,然后在這個注釋塊之中再嵌套成第二個多行注釋。終止注釋時先插入第二個注釋塊的終止標記,然后再插入第一個注釋塊的終止標記:

/* 這是第一個多行注釋的開頭
/* 這是第二個被嵌套的多行注釋 */
這是第一個多行注釋的結(jié)尾 */

通過運用嵌套多行注釋,你可以快速方便的注釋掉一大段代碼,即使這段代碼之中已經(jīng)含有了多行注釋塊。

分號

與其他大部分編程語言不同,Swift 并不強制要求你在每條語句的結(jié)尾處使用分號(;),當(dāng)然,你也可以按照你自己的習(xí)慣添加分號。有一種情況下必須要用分號,即你打算在同一行內(nèi)寫多條獨立的語句:

let cat = "??"; print(cat)
// 輸出“??”

整數(shù)

整數(shù)就是沒有小數(shù)部分的數(shù)字,比如 42-23 。整數(shù)可以是 有符號(正、負、零)或者 無符號(正、零)。

Swift 提供了8、16、32和64位的有符號和無符號整數(shù)類型。這些整數(shù)類型和 C 語言的命名方式很像,比如8位無符號整數(shù)類型是 UInt8,32位有符號整數(shù)類型是 Int32 。就像 Swift 的其他類型一樣,整數(shù)類型采用大寫命名法。

整數(shù)范圍

你可以訪問不同整數(shù)類型的 minmax 屬性來獲取對應(yīng)類型的最小值和最大值:

let minValue = UInt8.min  // minValue 為 0,是 UInt8 類型
let maxValue = UInt8.max  // maxValue 為 255,是 UInt8 類型

minmax 所傳回值的類型,正是其所對的整數(shù)類型(如上例 UInt8, 所傳回的類型是 UInt8),可用在表達式中相同類型值旁。

Int

一般來說,你不需要專門指定整數(shù)的長度。Swift 提供了一個特殊的整數(shù)類型 Int,長度與當(dāng)前平臺的原生字長相同:

  • 在32位平臺上,IntInt32 長度相同。
  • 在64位平臺上,IntInt64 長度相同。

除非你需要特定長度的整數(shù),一般來說使用 Int 就夠了。這可以提高代碼一致性和可復(fù)用性。即使是在32位平臺上,Int 可以存儲的整數(shù)范圍也可以達到 -2,147,483,648 ~ 2,147,483,647,大多數(shù)時候這已經(jīng)足夠大了。

UInt

Swift 也提供了一個特殊的無符號類型 UInt,長度與當(dāng)前平臺的原生字長相同:

  • 在32位平臺上,UIntUInt32 長度相同。
  • 在64位平臺上,UIntUInt64 長度相同。

注意

盡量不要使用 UInt,除非你真的需要存儲一個和當(dāng)前平臺原生字長相同的無符號整數(shù)。除了這種情況,最好使用 Int,即使你要存儲的值已知是非負的。統(tǒng)一使用 Int 可以提高代碼的可復(fù)用性,避免不同類型數(shù)字之間的轉(zhuǎn)換,并且匹配數(shù)字的類型推斷,請參考 類型安全和類型推斷。

浮點數(shù)

浮點數(shù)是有小數(shù)部分的數(shù)字,比如 3.14159、0.1-273.15

浮點類型比整數(shù)類型表示的范圍更大,可以存儲比 Int 類型更大或者更小的數(shù)字。Swift 提供了兩種有符號浮點數(shù)類型:

  • Double 表示64位浮點數(shù)。當(dāng)你需要存儲很大或者很高精度的浮點數(shù)時請使用此類型。
  • Float 表示32位浮點數(shù)。精度要求不高的話可以使用此類型。

注意

Double 精確度很高,至少有15位數(shù)字,而 Float 只有6位數(shù)字。選擇哪個類型取決于你的代碼需要處理的值的范圍,在兩種類型都匹配的情況下,將優(yōu)先選擇 Double。

類型安全和類型推斷

Swift 是一個類型安全(type safe)的語言。類型安全的語言可以讓你清楚地知道代碼要處理的值的類型。如果你的代碼需要一個 String,你絕對不可能不小心傳進去一個 Int

由于 Swift 是類型安全的,所以它會在編譯你的代碼時進行類型檢查(type checks),并把不匹配的類型標記為錯誤。這可以讓你在開發(fā)的時候盡早發(fā)現(xiàn)并修復(fù)錯誤。

當(dāng)你要處理不同類型的值時,類型檢查可以幫你避免錯誤。然而,這并不是說你每次聲明常量和變量的時候都需要顯式指定類型。如果你沒有顯式指定類型,Swift 會使用類型推斷(type inference)來選擇合適的類型。有了類型推斷,編譯器可以在編譯代碼的時候自動推斷出表達式的類型。原理很簡單,只要檢查你賦的值即可。

因為有類型推斷,和 C 或者 Objective-C 比起來 Swift 很少需要聲明類型。常量和變量雖然需要明確類型,但是大部分工作并不需要你自己來完成。

當(dāng)你聲明常量或者變量并賦初值的時候類型推斷非常有用。當(dāng)你在聲明常量或者變量的時候賦給它們一個字面量(literal value 或 literal)即可觸發(fā)類型推斷。(字面量就是會直接出現(xiàn)在你代碼中的值,比如 423.14159 。)

例如,如果你給一個新常量賦值 42 并且沒有標明類型,Swift 可以推斷出常量類型是 Int ,因為你給它賦的初始值看起來像一個整數(shù):

let meaningOfLife = 42
// meaningOfLife 會被推測為 Int 類型

同理,如果你沒有給浮點字面量標明類型,Swift 會推斷你想要的是 Double

let pi = 3.14159
// pi 會被推測為 Double 類型

當(dāng)推斷浮點數(shù)的類型時,Swift 總是會選擇 Double 而不是 Float

如果表達式中同時出現(xiàn)了整數(shù)和浮點數(shù),會被推斷為 Double 類型:

let anotherPi = 3 + 0.14159
// anotherPi 會被推測為 Double 類型

原始值 3 沒有顯式聲明類型,而表達式中出現(xiàn)了一個浮點字面量,所以表達式會被推斷為 Double 類型。

數(shù)值型字面量

整數(shù)字面量可以被寫作:

  • 一個十進制數(shù),沒有前綴
  • 一個二進制數(shù),前綴是 0b
  • 一個八進制數(shù),前綴是 0o
  • 一個十六進制數(shù),前綴是 0x

下面的所有整數(shù)字面量的十進制值都是 17:

let decimalInteger = 17
let binaryInteger = 0b10001       // 二進制的17
let octalInteger = 0o21           // 八進制的17
let hexadecimalInteger = 0x11     // 十六進制的17

浮點字面量可以是十進制(沒有前綴)或者是十六進制(前綴是 0x )。小數(shù)點兩邊必須有至少一個十進制數(shù)字(或者是十六進制的數(shù)字)。十進制浮點數(shù)也可以有一個可選的指數(shù)(exponent),通過大寫或者小寫的 e 來指定;十六進制浮點數(shù)必須有一個指數(shù),通過大寫或者小寫的 p 來指定。

如果一個十進制數(shù)的指數(shù)為 exp,那這個數(shù)相當(dāng)于基數(shù)和10^exp 的乘積:

  • 1.25e2 表示 1.25 × 10^2,等于 125.0
  • 1.25e-2 表示 1.25 × 10^-2,等于 0.0125。

如果一個十六進制數(shù)的指數(shù)為 exp,那這個數(shù)相當(dāng)于基數(shù)和2^exp 的乘積:

  • 0xFp2 表示 15 × 2^2,等于 60.0。
  • 0xFp-2 表示 15 × 2^-2,等于 3.75。

下面的這些浮點字面量都等于十進制的 12.1875

let decimalDouble = 12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0

數(shù)值類字面量可以包括額外的格式來增強可讀性。整數(shù)和浮點數(shù)都可以添加額外的零并且包含下劃線,并不會影響字面量:

let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1

數(shù)值型類型轉(zhuǎn)換

通常來講,即使代碼中的整數(shù)常量和變量已知非負,也請使用 Int 類型??偸鞘褂媚J的整數(shù)類型可以保證你的整數(shù)常量和變量可以直接被復(fù)用并且可以匹配整數(shù)類字面量的類型推斷。

只有在必要的時候才使用其他整數(shù)類型,比如要處理外部的長度明確的數(shù)據(jù)或者為了優(yōu)化性能、內(nèi)存占用等等。使用顯式指定長度的類型可以及時發(fā)現(xiàn)值溢出并且可以暗示正在處理特殊數(shù)據(jù)。

整數(shù)轉(zhuǎn)換

不同整數(shù)類型的變量和常量可以存儲不同范圍的數(shù)字。Int8 類型的常量或者變量可以存儲的數(shù)字范圍是 -128~127,而 UInt8 類型的常量或者變量能存儲的數(shù)字范圍是 0~255。如果數(shù)字超出了常量或者變量可存儲的范圍,編譯的時候會報錯:

let cannotBeNegative: UInt8 = -1
// UInt8 類型不能存儲負數(shù),所以會報錯
let tooBig: Int8 = Int8.max + 1
// Int8 類型不能存儲超過最大值的數(shù),所以會報錯

由于每種整數(shù)類型都可以存儲不同范圍的值,所以你必須根據(jù)不同情況選擇性使用數(shù)值型類型轉(zhuǎn)換。這種選擇性使用的方式,可以預(yù)防隱式轉(zhuǎn)換的錯誤并讓你的代碼中的類型轉(zhuǎn)換意圖變得清晰。

要將一種數(shù)字類型轉(zhuǎn)換成另一種,你要用當(dāng)前值來初始化一個期望類型的新數(shù)字,這個數(shù)字的類型就是你的目標類型。在下面的例子中,常量 twoThousandUInt16 類型,然而常量 oneUInt8 類型。它們不能直接相加,因為它們類型不同。所以要調(diào)用 UInt16(one) 來創(chuàng)建一個新的 UInt16 數(shù)字并用 one 的值來初始化,然后使用這個新數(shù)字來計算:

let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)

現(xiàn)在兩個數(shù)字的類型都是 UInt16,可以進行相加。目標常量 twoThousandAndOne 的類型被推斷為 UInt16,因為它是兩個 UInt16 值的和。

SomeType(ofInitialValue) 是調(diào)用 Swift 構(gòu)造器并傳入一個初始值的默認方法。在語言內(nèi)部,UInt16 有一個構(gòu)造器,可以接受一個 UInt8 類型的值,所以這個構(gòu)造器可以用現(xiàn)有的 UInt8 來創(chuàng)建一個新的 UInt16。注意,你并不能傳入任意類型的值,只能傳入 UInt16 內(nèi)部有對應(yīng)構(gòu)造器的值。不過你可以擴展現(xiàn)有的類型來讓它可以接收其他類型的值(包括自定義類型),請參考 擴展。

整數(shù)和浮點數(shù)轉(zhuǎn)換

整數(shù)和浮點數(shù)的轉(zhuǎn)換必須顯式指定類型:

let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
// pi 等于 3.14159,所以被推測為 Double 類型

這個例子中,常量 three 的值被用來創(chuàng)建一個 Double 類型的值,所以加號兩邊的數(shù)類型須相同。如果不進行轉(zhuǎn)換,兩者無法相加。

浮點數(shù)到整數(shù)的反向轉(zhuǎn)換同樣行,整數(shù)類型可以用 Double 或者 Float 類型來初始化:

let integerPi = Int(pi)
// integerPi 等于 3,所以被推測為 Int 類型

當(dāng)用這種方式來初始化一個新的整數(shù)值時,浮點值會被截斷。也就是說 4.75 會變成 4,-3.9 會變成 -3。

注意

結(jié)合數(shù)字類常量和變量不同于結(jié)合數(shù)字類字面量。字面量 3 可以直接和字面量 0.14159 相加,因為數(shù)字字面量本身沒有明確的類型。它們的類型只在編譯器需要求值的時候被推測。

類型別名

類型別名(type aliases)就是給現(xiàn)有類型定義另一個名字。你可以使用 typealias 關(guān)鍵字來定義類型別名。

當(dāng)你想要給現(xiàn)有類型起一個更有意義的名字時,類型別名非常有用。假設(shè)你正在處理特定長度的外部資源的數(shù)據(jù):

typealias AudioSample = UInt16

定義了一個類型別名之后,你可以在任何使用原始名的地方使用別名:

var maxAmplitudeFound = AudioSample.min
// maxAmplitudeFound 現(xiàn)在是 0

本例中,AudioSample 被定義為 UInt16 的一個別名。因為它是別名,AudioSample.min 實際上是 UInt16.min,所以會給 maxAmplitudeFound 賦一個初值 0。

布爾值

Swift 有一個基本的布爾(Boolean)類型,叫做 Bool。布爾值指邏輯上的值,因為它們只能是真或者假。Swift 有兩個布爾常量,truefalse

let orangesAreOrange = true
let turnipsAreDelicious = false

orangesAreOrangeturnipsAreDelicious 的類型會被推斷為 Bool,因為它們的初值是布爾字面量。就像之前提到的 IntDouble 一樣,如果你創(chuàng)建變量的時候給它們賦值 true 或者 false,那你不需要將常量或者變量聲明為 Bool 類型。初始化常量或者變量的時候如果所賦的值類型已知,就可以觸發(fā)類型推斷,這讓 Swift 代碼更加簡潔并且可讀性更高。

當(dāng)你編寫條件語句比如 if 語句的時候,布爾值非常有用:

if turnipsAreDelicious {
    print("Mmm, tasty turnips!")
} else {
    print("Eww, turnips are horrible.")
}
// 輸出“Eww, turnips are horrible.”

條件語句,例如 if,請參考 控制流。

如果你在需要使用 Bool 類型的地方使用了非布爾值,Swift 的類型安全機制會報錯。下面的例子會報告一個編譯時錯誤:

let i = 1
if i {
    // 這個例子不會通過編譯,會報錯
}

然而,下面的例子是合法的:

let i = 1
if i == 1 {
    // 這個例子會編譯成功
}

i == 1 的比較結(jié)果是 Bool 類型,所以第二個例子可以通過類型檢查。類似 i == 1 這樣的比較,請參考 基本操作符。

和 Swift 中的其他類型安全的例子一樣,這個方法可以避免錯誤并保證這塊代碼的意圖總是清晰的。

元組

元組(tuples)把多個值組合成一個復(fù)合值。元組內(nèi)的值可以是任意類型,并不要求是相同類型。

下面這個例子中,(404, "Not Found") 是一個描述 HTTP 狀態(tài)碼(HTTP status code)的元組。HTTP 狀態(tài)碼是當(dāng)你請求網(wǎng)頁的時候 web 服務(wù)器返回的一個特殊值。如果你請求的網(wǎng)頁不存在就會返回一個 404 Not Found 狀態(tài)碼。

let http404Error = (404, "Not Found")
// http404Error 的類型是 (Int, String),值是 (404, "Not Found")

(404, "Not Found") 元組把一個 Int 值和一個 String 值組合起來表示 HTTP 狀態(tài)碼的兩個部分:一個數(shù)字和一個人類可讀的描述。這個元組可以被描述為“一個類型為 (Int, String) 的元組”。

你可以把任意順序的類型組合成一個元組,這個元組可以包含所有類型。只要你想,你可以創(chuàng)建一個類型為 (Int, Int, Int) 或者 (String, Bool) 或者其他任何你想要的組合的元組。

你可以將一個元組的內(nèi)容分解(decompose)成單獨的常量和變量,然后你就可以正常使用它們了:

let (statusCode, statusMessage) = http404Error
print("The status code is \(statusCode)")
// 輸出“The status code is 404”
print("The status message is \(statusMessage)")
// 輸出“The status message is Not Found”

如果你只需要一部分元組值,分解的時候可以把要忽略的部分用下劃線(_)標記:

let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)")
// 輸出“The status code is 404”

此外,你還可以通過下標來訪問元組中的單個元素,下標從零開始:

print("The status code is \(http404Error.0)")
// 輸出“The status code is 404”
print("The status message is \(http404Error.1)")
// 輸出“The status message is Not Found”

你可以在定義元組的時候給單個元素命名:

let http200Status = (statusCode: 200, description: "OK")

給元組中的元素命名后,你可以通過名字來獲取這些元素的值:

print("The status code is \(http200Status.statusCode)")
// 輸出“The status code is 200”
print("The status message is \(http200Status.description)")
// 輸出“The status message is OK”

作為函數(shù)返回值時,元組非常有用。一個用來獲取網(wǎng)頁的函數(shù)可能會返回一個 (Int, String) 元組來描述是否獲取成功。和只能返回一個類型的值比較起來,一個包含兩個不同類型值的元組可以讓函數(shù)的返回信息更有用。請參考 函數(shù)參數(shù)與返回值

注意

當(dāng)遇到一些相關(guān)值的簡單分組時,元組是很有用的。元組不適合用來創(chuàng)建復(fù)雜的數(shù)據(jù)結(jié)構(gòu)。如果你的數(shù)據(jù)結(jié)構(gòu)比較復(fù)雜,不要使用元組,用類或結(jié)構(gòu)體去建模。欲獲得更多信息,請參考 結(jié)構(gòu)體和類。

可選類型

使用可選類型(optionals)來處理值可能缺失的情況??蛇x類型表示兩種可能: 或者有值, 你可以解析可選類型訪問這個值, 或者根本沒有值。

注意

C 和 Objective-C 中并沒有可選類型這個概念。最接近的是 Objective-C 中的一個特性,一個方法要不返回一個對象要不返回 nil,nil 表示“缺少一個合法的對象”。然而,這只對對象起作用——對于結(jié)構(gòu)體,基本的 C 類型或者枚舉類型不起作用。對于這些類型,Objective-C 方法一般會返回一個特殊值(比如 NSNotFound)來暗示值缺失。這種方法假設(shè)方法的調(diào)用者知道并記得對特殊值進行判斷。然而,Swift 的可選類型可以讓你暗示任意類型的值缺失,并不需要一個特殊值。

來看一個例子。Swift 的 Int 類型有一種構(gòu)造器,作用是將一個 String 值轉(zhuǎn)換成一個 Int 值。然而,并不是所有的字符串都可以轉(zhuǎn)換成一個整數(shù)。字符串 "123" 可以被轉(zhuǎn)換成數(shù)字 123 ,但是字符串 "hello, world" 不行。

下面的例子使用這種構(gòu)造器來嘗試將一個 String 轉(zhuǎn)換成 Int

let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
// convertedNumber 被推測為類型 "Int?", 或者類型 "optional Int"

因為該構(gòu)造器可能會失敗,所以它返回一個可選類型(optional)Int,而不是一個 Int。一個可選的 Int 被寫作 Int? 而不是 Int。問號暗示包含的值是可選類型,也就是說可能包含 Int 值也可能不包含值。(不能包含其他任何值比如 Bool 值或者 String 值。只能是 Int 或者什么都沒有。)

nil

你可以給可選變量賦值為 nil 來表示它沒有值:

var serverResponseCode: Int? = 404
// serverResponseCode 包含一個可選的 Int 值 404
serverResponseCode = nil
// serverResponseCode 現(xiàn)在不包含值

注意

nil 不能用于非可選的常量和變量。如果你的代碼中有常量或者變量需要處理值缺失的情況,請把它們聲明成對應(yīng)的可選類型。

如果你聲明一個可選常量或者變量但是沒有賦值,它們會自動被設(shè)置為 nil

var surveyAnswer: String?
// surveyAnswer 被自動設(shè)置為 nil

注意

Swift 的 nil 和 Objective-C 中的 nil 并不一樣。在 Objective-C 中,nil 是一個指向不存在對象的指針。在 Swift 中,nil 不是指針——它是一個確定的值,用來表示值缺失。任何類型的可選狀態(tài)都可以被設(shè)置為 nil,不只是對象類型。

if 語句以及強制解析

你可以使用 if 語句和 nil 比較來判斷一個可選值是否包含值。你可以使用“相等”(==)或“不等”(!=)來執(zhí)行比較。

如果可選類型有值,它將不等于 nil

if convertedNumber != nil {
    print("convertedNumber contains some integer value.")
}
// 輸出“convertedNumber contains some integer value.”

當(dāng)你確定可選類型確實包含值之后,你可以在可選的名字后面加一個感嘆號(!)來獲取值。這個驚嘆號表示“我知道這個可選有值,請使用它?!边@被稱為可選值的強制解析(forced unwrapping)

if convertedNumber != nil {
    print("convertedNumber has an integer value of \(convertedNumber!).")
}
// 輸出“convertedNumber has an integer value of 123.”

更多關(guān)于 if 語句的內(nèi)容,請參考 控制流

注意

使用 ! 來獲取一個不存在的可選值會導(dǎo)致運行時錯誤。使用 ! 來強制解析值之前,一定要確定可選包含一個非 nil 的值。

可選綁定

使用可選綁定(optional binding)來判斷可選類型是否包含值,如果包含就把值賦給一個臨時常量或者變量??蛇x綁定可以用在 ifwhile 語句中,這條語句不僅可以用來判斷可選類型中是否有值,同時可以將可選類型中的值賦給一個常量或者變量。ifwhile 語句,請參考 控制流。

像下面這樣在 if 語句中寫一個可選綁定:

if let constantName = someOptional {
    statements
}

你可以像上面這樣使用可選綁定來重寫 在 可選類型 舉出的 possibleNumber 例子:

if let actualNumber = Int(possibleNumber) {
    print("\'\(possibleNumber)\' has an integer value of \(actualNumber)")
} else {
    print("\'\(possibleNumber)\' could not be converted to an integer")
}
// 輸出“'123' has an integer value of 123”

這段代碼可以被理解為:

“如果 Int(possibleNumber) 返回的可選 Int 包含一個值,創(chuàng)建一個叫做 actualNumber 的新常量并將可選包含的值賦給它。”

如果轉(zhuǎn)換成功,actualNumber 常量可以在 if 語句的第一個分支中使用。它已經(jīng)被可選類型 包含的 值初始化過,所以不需要再使用 ! 后綴來獲取它的值。在這個例子中,actualNumber 只被用來輸出轉(zhuǎn)換結(jié)果。

你可以在可選綁定中使用常量和變量。如果你想在 if 語句的第一個分支中操作 actualNumber 的值,你可以改成 if var actualNumber,這樣可選類型包含的值就會被賦給一個變量而非常量。

你可以包含多個可選綁定或多個布爾條件在一個 if 語句中,只要使用逗號分開就行。只要有任意一個可選綁定的值為 nil,或者任意一個布爾條件為 false,則整個 if 條件判斷為 false。下面的兩個 if 語句是等價的:

if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
    print("\(firstNumber) < \(secondNumber) < 100")
}
// 輸出“4 < 42 < 100”

if let firstNumber = Int("4") {
    if let secondNumber = Int("42") {
        if firstNumber < secondNumber && secondNumber < 100 {
            print("\(firstNumber) < \(secondNumber) < 100")
        }
    }
}
// 輸出“4 < 42 < 100”

注意

if 條件語句中使用常量和變量來創(chuàng)建一個可選綁定,僅在 if 語句的句中(body)中才能獲取到值。相反,在 guard 語句中使用常量和變量來創(chuàng)建一個可選綁定,僅在 guard 語句外且在語句后才能獲取到值,請參考 提前退出。

隱式解析可選類型

如上所述,可選類型暗示了常量或者變量可以“沒有值”??蛇x可以通過 if 語句來判斷是否有值,如果有值的話可以通過可選綁定來解析值。

有時候在程序架構(gòu)中,第一次被賦值之后,可以確定一個可選類型總會有值。在這種情況下,每次都要判斷和解析可選值是非常低效的,因為可以確定它總會有值。

這種類型的可選狀態(tài)被定義為隱式解析可選類型(implicitly unwrapped optionals)。把想要用作可選的類型的后面的問號(String?)改成感嘆號(String!)來聲明一個隱式解析可選類型。

當(dāng)可選類型被第一次賦值之后就可以確定之后一直有值的時候,隱式解析可選類型非常有用。隱式解析可選類型主要被用在 Swift 中類的構(gòu)造過程中,請參考 無主引用以及隱式解析可選屬性。

一個隱式解析可選類型其實就是一個普通的可選類型,但是可以被當(dāng)做非可選類型來使用,并不需要每次都使用解析來獲取可選值。下面的例子展示了可選類型 String 和隱式解析可選類型 String 之間的區(qū)別:

let possibleString: String? = "An optional string."
let forcedString: String = possibleString! // 需要感嘆號來獲取值

let assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString  // 不需要感嘆號

你可以把隱式解析可選類型當(dāng)做一個可以自動解析的可選類型。你要做的只是聲明的時候把感嘆號放到類型的結(jié)尾,而不是每次取值的可選名字的結(jié)尾。

注意

如果你在隱式解析可選類型沒有值的時候嘗試取值,會觸發(fā)運行時錯誤。和你在沒有值的普通可選類型后面加一個驚嘆號一樣。

你仍然可以把隱式解析可選類型當(dāng)做普通可選類型來判斷它是否包含值:

if assumedString != nil {
    print(assumedString!)
}
// 輸出“An implicitly unwrapped optional string.”

你也可以在可選綁定中使用隱式解析可選類型來檢查并解析它的值:

if let definiteString = assumedString {
    print(definiteString)
}
// 輸出“An implicitly unwrapped optional string.”

注意

如果一個變量之后可能變成 nil 的話請不要使用隱式解析可選類型。如果你需要在變量的生命周期中判斷是否是 nil 的話,請使用普通可選類型。

錯誤處理

你可以使用 錯誤處理(error handling) 來應(yīng)對程序執(zhí)行中可能會遇到的錯誤條件。

相對于可選中運用值的存在與缺失來表達函數(shù)的成功與失敗,錯誤處理可以推斷失敗的原因,并傳播至程序的其他部分。

當(dāng)一個函數(shù)遇到錯誤條件,它能報錯。調(diào)用函數(shù)的地方能拋出錯誤消息并合理處理。

func canThrowAnError() throws {
    // 這個函數(shù)有可能拋出錯誤
}

一個函數(shù)可以通過在聲明中添加 throws 關(guān)鍵詞來拋出錯誤消息。當(dāng)你的函數(shù)能拋出錯誤消息時,你應(yīng)該在表達式中前置 try 關(guān)鍵詞。

do {
    try canThrowAnError()
    // 沒有錯誤消息拋出
} catch {
    // 有一個錯誤消息拋出
}

一個 do 語句創(chuàng)建了一個新的包含作用域,使得錯誤能被傳播到一個或多個 catch 從句。

這里有一個錯誤處理如何用來應(yīng)對不同錯誤條件的例子。

func makeASandwich() throws {
    // ...
}

do {
    try makeASandwich()
    eatASandwich()
} catch SandwichError.outOfCleanDishes {
    washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
    buyGroceries(ingredients)
}

在此例中,makeASandwich()(做一個三明治)函數(shù)會拋出一個錯誤消息如果沒有干凈的盤子或者某個原料缺失。因為 makeASandwich() 拋出錯誤,函數(shù)調(diào)用被包裹在 try 表達式中。將函數(shù)包裹在一個 do 語句中,任何被拋出的錯誤會被傳播到提供的 catch 從句中。

如果沒有錯誤被拋出,eatASandwich() 函數(shù)會被調(diào)用。如果一個匹配 SandwichError.outOfCleanDishes 的錯誤被拋出,washDishes() 函數(shù)會被調(diào)用。如果一個匹配 SandwichError.missingIngredients 的錯誤被拋出,buyGroceries(_:) 函數(shù)會被調(diào)用,并且使用 catch 所捕捉到的關(guān)聯(lián)值 [String] 作為參數(shù)。

拋出,捕捉,以及傳播錯誤會在 錯誤處理 章節(jié)詳細說明。

斷言和先決條件

斷言和先決條件是在運行時所做的檢查。你可以用他們來檢查在執(zhí)行后續(xù)代碼之前是否一個必要的條件已經(jīng)被滿足了。如果斷言或者先決條件中的布爾條件評估的結(jié)果為 true(真),則代碼像往常一樣繼續(xù)執(zhí)行。如果布爾條件評估結(jié)果為 false(假),程序的當(dāng)前狀態(tài)是無效的,則代碼執(zhí)行結(jié)束,應(yīng)用程序中止。

你使用斷言和先決條件來表達你所做的假設(shè)和你在編碼時候的期望。你可以將這些包含在你的代碼中。斷言幫助你在開發(fā)階段找到錯誤和不正確的假設(shè),先決條件幫助你在生產(chǎn)環(huán)境中探測到存在的問題。

除了在運行時驗證你的期望值,斷言和先決條件也變成了一個在你的代碼中的有用的文檔形式。和在上面討論過的 錯誤處理 不同,斷言和先決條件并不是用來處理可以恢復(fù)的或者可預(yù)期的錯誤。因為一個斷言失敗表明了程序正處于一個無效的狀態(tài),沒有辦法去捕獲一個失敗的斷言。

使用斷言和先決條件不是一個能夠避免出現(xiàn)程序出現(xiàn)無效狀態(tài)的編碼方法。然而,如果一個無效狀態(tài)程序產(chǎn)生了,斷言和先決條件可以強制檢查你的數(shù)據(jù)和程序狀態(tài),使得你的程序可預(yù)測的中止(譯者:不是系統(tǒng)強制的,被動的中止),并幫助使這個問題更容易調(diào)試。一旦探測到無效的狀態(tài),執(zhí)行則被中止,防止無效的狀態(tài)導(dǎo)致的進一步對于系統(tǒng)的傷害。

斷言和先決條件的不同點是,他們什么時候進行狀態(tài)檢測:斷言僅在調(diào)試環(huán)境運行,而先決條件則在調(diào)試環(huán)境和生產(chǎn)環(huán)境中運行。在生產(chǎn)環(huán)境中,斷言的條件將不會進行評估。這個意味著你可以使用很多斷言在你的開發(fā)階段,但是這些斷言在生產(chǎn)環(huán)境中不會產(chǎn)生任何影響。

使用斷言進行調(diào)試

你可以調(diào)用 Swift 標準庫的 assert(_:_:file:line:) 函數(shù)來寫一個斷言。向這個函數(shù)傳入一個結(jié)果為 true 或者 false 的表達式以及一條信息,當(dāng)表達式的結(jié)果為 false 的時候這條信息會被顯示:

let age = -3
assert(age >= 0, "A person's age cannot be less than zero")
// 因為 age < 0,所以斷言會觸發(fā)

在這個例子中,只有 age >= 0true 時,即 age 的值非負的時候,代碼才會繼續(xù)執(zhí)行。如果 age 的值是負數(shù),就像代碼中那樣,age >= 0false,斷言被觸發(fā),終止應(yīng)用。

如果不需要斷言信息,可以就像這樣忽略掉:

assert(age >= 0)

如果代碼已經(jīng)檢查了條件,你可以使用 assertionFailure(_:file:line:) 函數(shù)來表明斷言失敗了,例如:

if age > 10 {
    print("You can ride the roller-coaster or the ferris wheel.")
} else if age > 0 {
    print("You can ride the ferris wheel.")
} else {
    assertionFailure("A person's age can't be less than zero.")
}

強制執(zhí)行先決條件

當(dāng)一個條件可能為假,但是繼續(xù)執(zhí)行代碼要求條件必須為真的時候,需要使用先決條件。例如使用先決條件來檢查是否下標越界,或者來檢查是否將一個正確的參數(shù)傳給函數(shù)。

你可以使用全局 precondition(_:_:file:line:) 函數(shù)來寫一個先決條件。向這個函數(shù)傳入一個結(jié)果為 true 或者 false 的表達式以及一條信息,當(dāng)表達式的結(jié)果為 false 的時候這條信息會被顯示:

// 在一個下標的實現(xiàn)里...
precondition(index > 0, "Index must be greater than zero.")

你可以調(diào)用 preconditionFailure(_:file:line:) 方法來表明出現(xiàn)了一個錯誤,例如,switch 進入了 default 分支,但是所有的有效值應(yīng)該被任意一個其他分支(非 default 分支)處理。

注意

如果你使用 unchecked 模式(-Ounchecked)編譯代碼,先決條件將不會進行檢查。編譯器假設(shè)所有的先決條件總是為 true(真),他將優(yōu)化你的代碼。然而,fatalError(_:file:line:) 函數(shù)總是中斷執(zhí)行,無論你怎么進行優(yōu)化設(shè)定。

你能使用 fatalError(_:file:line:) 函數(shù)在設(shè)計原型和早期開發(fā)階段,這個階段只有方法的聲明,但是沒有具體實現(xiàn),你可以在方法體中寫上 fatalError("Unimplemented")作為具體實現(xiàn)。因為 fatalError 不會像斷言和先決條件那樣被優(yōu)化掉,所以你可以確保當(dāng)代碼執(zhí)行到一個沒有被實現(xiàn)的方法時,程序會被中斷。

基本運算符 ?
?