Swift 語言基礎

xslidian發表於2014-06-03

雖說 Swift 是開發 iOS 及 OS X 應用的一門新程式語言,但它的開發體驗與 C 或 Objective-C 有很多相似之處。

Swift 重新實現了 C 與 Objective-C 中的所有基礎型別,包括表示整數的 Int,表示浮點數的 DoubleFloat,表示布林值的 Bool,以及表示純文字資料的 String。 Swift 還為兩個基本集合型別 ArrayDictionary 提供了強大的支援,詳情可參考 集合型別

與 C 語言類似,Swift 也採用變數儲存資料,並通過識別符號來引用變數值。 Swift 還擴充了值不可變的量——常量的用法,使它比 C 語言中的常量強大得多。 當在 Swift 中操作不需要改變值的資料時,使用常量可使程式碼更安全、更簡潔。

除常見型別以外,Swift 還引入了 Objective-C 中不存在的高階型別,其中包括元組(tuple),可以新建或傳遞一組值。函式可以將多個值作為整體(單個元組值)返回給呼叫方。

Swift 還引入了可選量,可處理不存在的值。可選量可以“存在一個值 x”,也可以“不存在任何值”。可選量與 Objective-C 為指標賦 nil 相似,但在 Swift 中可以對任意型別使用,而不只針對類。可選量比 Objective-C 的 nil 指標更安全且語義更豐富,在 Swift 最強大的諸多功能中得到了深入的應用。

可選量是 Swift 型別安全的一點體現。Swift 可幫助你清晰地瞭解程式碼能處理的資料型別。如果程式碼希望得到 String 資料,型別安全的特性將阻止你偶然將 Int 傳遞過去。這樣可以在開發過程中儘可能早地發現與修正問題。

常量與變數

常量及變數將名稱(如maximumNumberOfLoginAttemptswelcomeMessage) 與特定型別的值(如數字 10 或字串 "Hello")關聯起來。常量一旦賦值,其值不可再改變;而變數以後還可以改賦不同的值。

常量及變數的宣告

常量與變數在使用之前必須宣告。使用 let 關鍵詞宣告常量,使用 var 關鍵詞宣告變數。 下面是可以跟蹤使用者登入次數的常量與變數的例子:

  1. let maximumNumberOfLoginAttempts = 10 // 允許嘗試登入的次數
  2. var currentLoginAttempt = 0           // 已經嘗試登入的次數

這段程式碼可以這樣理解:

“宣告一個新的常量,其名稱為 maximumNumberOfLoginAttempts,並將其賦值為 10。 然後,宣告一個新的變數,其名稱為 currentLoginAttempt,並賦初始值為 0。”

在本例中,允許嘗試的最多登入次數作為常量宣告,因為允許的次數在執行時永遠不會發生變化。當前已嘗試次數的計數器作為變數宣告,因為該值必須在登入失敗時遞增。

可以在同一行宣告多個常量或變數,以逗號分隔:

  1. var x = 0.0, y = 0.0, z = 0.0

提示:如果程式碼中需要儲存的值不會改變,務必通過 let 關鍵字作為常量宣告。只有儲存需要改變的值時才需要使用變數。

型別說明

宣告常量或變數時可提供型別說明,明確指定該常量或變數所能儲存的資料型別。型別說明的寫法為,在常量或變數名稱後加上一個冒號、一個空格,後接要使用的型別名稱。

本例為名為 welcomeMessage 的變數提供型別說明,指明該變數可儲存 String 型的值:

  1. var welcomeMessage: String

宣告語句中的冒號意為“…的型別為…”,因此上面的程式碼可以這樣理解:

“宣告一個名為 welcomeMessage 的變數,其型別為 String。”

其中“型別為 String”代表“可儲存任意 String 型別的值”。可以理解為可以存放的“東西的型別”(或“東西的種類”)。

welcomeMessage 變數現在可以賦任意字串值,不會報錯:

  1. welcomeMessage = "Hello"

提示:在實踐中需要編寫型別說明的情況非常罕見。如果你在定義常量或變數時提供了初始值,Swift 通常能夠推斷出該常量或變數應使用的型別,詳情參見 型別安全及型別推斷。在上述 welcomeMessage 例子中,沒有提供初始值,因此才通過型別說明給 welcomeMessage 變數明確指定型別,而沒有讓它通過初始值推斷。

常量與變數的命名

常量及變數的名稱可以使用幾乎所有字元,包括 Unicode 字元:

  1. let π = 3.14159
  2. let 你好 = "你好世界"
  3. let ?? = "汪哞"

常量及變數的名稱不可以包含數學符號、箭頭、私有(即無效的)Unicode 碼位 或繪製線條/方框用的字元。並且名稱不能以數字開頭, 但除了開頭的其他地方都可以使用數字。

常量及變數一旦宣告為一個特定型別,則不能以同樣的名稱再次宣告,也不能更改使其存放不同型別的值。同樣,也不允許將常量再次宣告為變數,或將變數再次宣告為常量。

提示:如果需要將常量或變數命名為 Swift 的保留字,可以在將該關鍵字作為名稱使用時用反引號(`)包圍。儘管如此,你還是應該避免將關鍵字作為名稱使用,除非迫不得已。

可以將現有變數改賦相容型別的值。在下例中,friendlyWelcome 被從 "Hello!" 改為 "Bonjour!"

  1. var friendlyWelcome = "Hello!"
  2. friendlyWelcome = "Bonjour!"
  3. // friendlyWelcome 現在為 "Bonjour!"

與變數不同,常量的值一旦確定則不可更改。嘗試更改則會報編譯錯誤:

  1. let languageName = "Swift"
  2. languageName = "Swift++"
  3. // 編譯時錯誤 - languageName 不可更改

輸出常量及變數

可以通過 println 函式輸出常量或變數的當前值:

  1. println(friendlyWelcome)
  2. // 輸出 "Bonjour!"

println 是一個全域性函式,可向適當的輸出介面輸出值,末尾接換行符。例如在 Xcode 環境開發,println 會將輸出內容輸出至 Xcode 的“控制檯”皮膚。(另一個函式 print 執行幾乎一樣的操作,不同之處在於,後者不會在待輸出值的末尾新增換行符。)

println 函式可輸出傳給它的任何 String 值:

  1. println("這是一個字串")
  2. // 輸出 "這是一個字串"

println 還能輸出更復雜的日誌訊息,用法與 Cocoa 的 NSLog 函式相似譯註:即 C 語言的 printf() 風格。訊息內容可以包括常量與變數的當前值。

Swift 通過字串內插string interpolation)將常量或變數的名稱作為佔位符內嵌到較長的字串中,藉此提示 Swift 將其替換為常量或變數的當前值。將名稱置於括號之間,並在左括號之前通過反斜槓轉義:

  1. println("friendlyWelcome 當前的值為 \(friendlyWelcome)")
  2. // 輸出 "friendlyWelcome 當前的值為 Bonjour!"

注:字串內插可用的所有選項參見 字串內插 一節。

註釋

通過註釋,可在程式碼中嵌入不可執行的文字,作為筆記或對自己的提示。在編譯程式碼時,Swift 編譯器會忽略註釋內容。

Swift 中的註釋與 C 語言非常相似。單行註釋以兩個左斜槓開頭(//):

  1. // 這是一條註釋

還可以編寫多行註釋,以左斜槓加星號(/*)開頭,並以星號加左斜槓(*/)結尾:

  1. /* 這也是一條註釋,
  2. 但跨越多行 */

與 C 語言的多行註釋有所不同的是,Swift 的多行註釋可以巢狀在其他多行註釋內部。寫法是在一個多行註釋塊內插入另一個多行註釋。第二個註釋塊封閉時,後面仍然接著第一個註釋塊:

  1. /* 這是第一個多行註釋的開頭
  2. /* 這是巢狀的第二個多行註釋 */
  3. 這是第一個多行註釋的結尾 */

巢狀式多行註釋允許你迅速簡便地註釋掉大段程式碼,而不用擔心這段程式碼中是否已經存在多行註釋。

分號

與其他很多語言不同,Swift 不要求在每條語句末尾加上分號(;),但只要你願意,加上也無妨。不過,如果要在同一行書寫多條語句,分號是必需的:

  1. let = "?"; println()
  2. // 輸出 "?"

    整數

整數(integer)指沒有小數部分的整數,如 42-23。整數既可以是有符號的signed,正數、零、負數)也可以是無符號的unsigned,正數或零)。

Swift 提供 8、16、32、64 位形式的有符號及無符號整數。這些整數型別遵循 C 語言的命名規約,如 8 位無符號整數的型別為 UInt8,32 位有符號整數的型別為 Int32。與 Swift 中的所有型別一樣,這些整數型別的名稱以大寫字母開頭。

整數的邊界

各整數型別允許的最小值及最大值可通過 minmax 屬性獲得:

  1. let 最小值 = UInt8.min // 最小值 等於 0,型別為 UInt8
  2. let 最大值 = UInt8.max // 最大值 等於 255,型別為 UInt8

這些屬性的值的型別與對應寬度的資料型別一致(如上例為 UInt8),因此也可以在表示式中與同型別的其他數值一起使用。

Int

絕大多數情況下,你並不需要自己決定程式碼中要使用的整數寬度。Swift 還提供了一個整數型別 Int,其寬度與當前平臺的原生字長(word size)一致:

  • 在 32 位平臺,IntInt32 寬度一致。
  • 在 64 位平臺,IntInt64 寬度一致。

除非你需要處理特定寬度的整數,在程式碼中應該只使用 Int 表示整數。這樣可以保證一致性及互運算性。即使是在 32 位平臺,Int 也能儲存 -2,147,483,6482,147,483,647 的任意數值,對於很多整數區間需求來說已經足夠大了。譯註:信蘋果會丟飯碗的

UInt

Swift 還提供了無符號整數型別 UInt,其寬度與當前平臺的原生字長一致:

  • 在 32 位平臺,UIntUInt32 寬度一致。
  • 在 64 位平臺,UIntUInt64 寬度一致。

注:只有在特別需要寬度與平臺原生字長一致的時才需要使用無整數型別 UInt。否則應使用 Int,即使要儲存的值一定非負。總使用 Int 表示整數值有助於保證程式碼互運算性、避免不同資料型別的轉換,並且與整數型別推斷相匹配,參見 型別安全及型別推斷

浮點數

浮點數 表示有小數部分的數字,例如 3.141590.1-273.15

浮點數型別可以表示的值比整數型別寬廣得多,也能儲存 Int 型別能存放的最大及最小值。Swift 提供兩種有符號的浮點數型別:

  • Double 表示一個 64 位浮點數。在浮點數值非常大或非常精確時使用它。
  • Float 表示一個 32 位浮點數。在浮點數值不需要 64 位精度時使用它。

Double 的精度為 15 個十進位制有效數字,而 Float 的精度只有 6 位十進位制有效數字。應根據程式碼所需數值的特點及值域選用合適的浮點數型別。

型別安全及型別推斷

Swift 是一門型別安全的語言。型別安全要求程式碼中值的型別非常明確。如果程式碼中要求提供 String 資料,則不會錯誤地向它傳遞 Int 資料。

由於 Swift 型別安全,它會在編譯程式碼時執行型別檢查,並將任何型別不匹配的地方標為錯誤。這樣可以在開發過程中儘可能早地發現並修復問題。

型別檢查有助於在操作不同型別值時避免錯誤。然而,這並不意味著你必須為宣告的每個常量與變數指定型別。如果你不指定所需值的型別,Swift 會通過型別推斷type inference)求得適當的型別。型別推斷允許編譯器在編譯程式碼時,根據你提供的值,自動推測出特定表示式的型別。

得益於型別推斷,Swift 對型別宣告的需要比起 C 或 Objective-C 語言而言要少很多。常量與變數仍然有明確的型別,但明確指定型別的工作已經由編譯器代你完成。

型別推斷在你宣告常量或變數的同時提供初始值時尤其有用。通常通過在宣告時賦字面值literal value,或稱“字面量literal)實現。(字面值指直接出現在原始碼中的值,如下例中的 423.14159。)

例如,如果將字面值 42 賦給新的常量,而不明確講它是什麼型別,Swift 會推斷出你希望該常量為 Int 型別,因為你初始化時提供的數字像是個整數:

  1. let meaningOfLife = 42
  2. // meaningOfLife 被推斷屬於 Int 型別

類似地,如果不為浮點數字面量指定型別,Swift 會推斷出你希望建立一個 Double 變數:

  1. let pi = 3.14159
  2. // pi 被推斷屬於 Double 型別

Swift 在推斷浮點數型別時總會選用 Double(而不用 Float)。

如果在表示式中同時使用整數與浮點數字面量,將根據上下文推斷得到 Double 型別:

  1. let anotherPi = 3 + 0.14159
  2. // anotherPi 也被推斷為 Double 型別

字面值 3 沒有明確的型別,自身也不屬於某個明確的型別,但由於加法中出現了浮點數字面量,因此推斷出合適的輸出型別為 Double

數字字面量

整數字面量可以以下面的形式書寫:

  • 十進位制數,無需字首
  • 二進位制數,以 0b 為字首
  • 八進位制數,以 0o 為字首
  • 十六進位制數,以 0x 為字首

下述整數字面量的值均為十進位制的 17

  1. let 十進位制整數 = 17
  2. let 二進位制整數 = 0b10001 // 17 的二進位制表示
  3. let 八進位制整數 = 0o21 // 17 的八進位制表示
  4. let 十六進位制整數 = 0x11 // 17 的十六進位制表示

浮點數字面值可以為十進位制(無需字首),也可以是十六進位制(以 0x 為字首)。小數點兩側均必須有數字(或十六進位制數字)。還可以有一個可選的冪次exponent),對十進位制浮點數為大寫或小寫的 e,對十六進位制浮點數為大寫或小寫的 p

對冪次為 exp 的十進位制數,基數將乘以 10exp

  • 1.25e2 即 1.25 × 102125.0
  • 1.25e-2 即 1.25 × 10-20.0125

對冪次為 exp 的十六進位制數,基數將乘以 2exp

  • 0xFp2 即 15 × 2260.0
  • 0xFp-2 即 15 × 2-23.75

下述所有浮點數字面量的值均為十進位制的 12.1875

  1. let 十進位制雙精度浮點數 = 12.1875
  2. let 冪次表示的雙精度浮點數 = 1.21875e1
  3. let 十六進位制雙精度浮點數 = 0xC.3p0

數字字面量可以包含額外的格式以便於閱讀。整數與浮點數均可以新增多餘的零或下劃線以提高可讀性。兩種格式均不會影響字面量的實際值:

  1. let 經填充的雙精度浮點數 = 000123.456
  2. let 一百萬 = 1_000_000
  3. let 一百萬多一點點 = 1_000_000.000_000_1

    數字型別轉換

Int 型別應作為所有常規用途的整數常量及變數的型別,即使它們確實非負。通常情況下,使用預設的整數型別意味著這些整型常量與變數均可即時互相參與運算,並可與根據整數字面值推斷出的型別相匹配。

僅當手中的任務必須使用其他整數型別時才用它們,如外部資料來源提供寬度明確的資料,或為了效能、記憶體佔用等其他必需優化考慮。在這些情況下使用寬度明確的型別有助於發現偶然的數值溢位,並還原這些資料實際使用時的特點。

整數轉換

不同型別的整數常量或變數所能儲存的值域不同。Int8 常量或變數能儲存 -128127,而 UInt8 常量或變數能儲存 0255。無法存放進某常量或變數的數字會報編譯時錯誤:

  1. let cannotBeNegative: UInt8 = -1
  2. // UInt8 不能儲存負數,因此會報錯
  3. let tooBig: Int8 = Int8.max + 1
  4. // Int8 不能儲存大於其最大值的數字,
  5. // 因此這裡也會報錯

由於不同資料型別能儲存的值域不同,在進行資料轉換時需要具體問題具體對待。這種實際選擇的過程可避免隱式轉換的問題,並使型別轉換的意圖在程式碼中明確地展現出來。

要將一種資料型別轉換為另一種,應使用現有值初始化一個所需型別的新數。下例中,常量 twoThousand 的型別為 UInt16,而常量 one 的型別為 UInt8。它們無法直接相加,因為型別不同。因此,本例將呼叫 UInt16(one) 新建一個 UInt16 數,並以 one 的數值初始化,並將新值放在呼叫處:

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

現在加號兩側均為 UInt16 型別,因此允許相加。輸出的常量 (twoThousandAndOne) 推斷得出的型別為 UInt16,因為它是兩個 UInt16 值的和。

某型別(賦初始值) 是呼叫 Swift 型別建構函式並傳遞初始值的預設方法。幕後運作情況是,UInt16 有一個接受 UInt8 值的建構函式,因此該建構函式會被用於根據現有 UInt8 建立新的 UInt16。不過,在這裡並不能傳入任意型別——只能傳入 UInt16 提供有建構函式的型別。擴充套件現有型別使其提供接受新型別(包括自己定義的型別)的建構函式的方法請見 擴充套件 一章。

整數與浮點數轉換

整數與浮點數型別之間的轉換必須顯式指定:

  1. let three = 3
  2. let pointOneFourOneFiveNine = 0.14159
  3. let pi = Double(three) + pointOneFourOneFiveNine
  4. // pi 等於 3.14159,推斷得到的型別為 Double

這段程式碼中,常量 three 被用來建立新的 Double 型別值,這樣加法兩側的型別才一致。不進行型別轉換的話,兩側將不允許相加。

浮點數到整數的逆向轉換同樣可行,整數型別可以用 DoubleFloat 值初始化:

  1. let integerPi = Int(pi)
  2. // integerPi 等於 3,推斷得到的型別為 Int

這樣用浮點數初始化新整數時,浮點數值總會被截斷。即, 4.75 變為 4-3.9 變為 -3

多個數字常量或變數的結合規則與數字字面量的結合規則不同。字面量 3 可以直接與字面值 0.14159 相加,因為數字字面量沒有明確指定型別,它們自身也沒有明確的型別。其型別僅當被編譯器求值時才推斷得出。

型別別名

型別別名type aliases)為現有型別定義可替代的名稱。型別別名通過 typealias 關鍵字定義。

型別別名在需要以上下文中更為合適的名稱稱呼某個現有型別時非常有用,例如當處理來自外部資料來源的特定寬度的資料時:

  1. typealias 音訊取樣 = UInt16

型別別名定義完成後,即可在可能用到原名的地方使用別名:

  1. var 已發現的最大振幅 = 音訊取樣.min
  2. // 已發現的最大振幅 現在為 0

此處 音訊取樣 作為 UInt16 的別名定義。因為它是別名,因此對 音訊取樣.min 的呼叫實際上會呼叫 UInt16.min,最終為 已發現的最大振幅 變數提供初始值 0

布林值

Swift 實現了基本的布林boolean)型別,稱為 Bool。布林值也稱為邏輯值logical),因為只能為true)或false)。Swift 提供了兩種布林常量值:truefalse

  1. let 橘子是橘子 = true
  2. let 蕪菁很好吃 = false

橘子是橘子蕪菁很好吃 的型別被推斷為 Bool,因為它們以布林字面值初始化。與上文中的 IntDouble 一樣,並不需要明確宣告為 Bool,只要在建立變數的同時用 truefalse 初始化。以已知型別的值初始化常量或變數時,型別推斷使 Swift 的程式碼更簡練、更具可讀性。

控制條件語句(如 if 語句)時,布林值尤其有用:

  1. if 蕪菁很好吃 {
  2.     println("唔,蕪菁真香!")
  3. } else {
  4.     println("呸,噁心死了。")
  5. }
  6. // 輸出 "呸,噁心死了。"

if 等條件語句的詳細情況請見 流程控制

Swift 的型別安全特性可避免非布林值被當作 Bool 使用。下面的例子會報編譯時錯誤:

  1. let i = 1
  2. if i {
  3.     // 本例無法通過編譯,報編譯錯誤
  4. }

換成下例便可以通過:

  1. let i = 1
  2. if i == 1 {
  3.     // 本例可成功編譯
  4. }

i == 1 比較的結果型別為 Bool,因此第二個例子可以通過型別檢查。i == 1 這類的比較在 基本運算子 一章討論。

與 Swift 中的其他型別檢查規則一樣,這些規則可避免偶然錯誤,並確保各段程式碼的目的總是明確的。

元組

元組將多個值組合為單個值。元組內的值可以是任意型別,各元素不必是相同的型別。

在本例中,(404, "Not Found") 是描述一條 HTTP 狀態碼HTTP status code)的元組。HTTP 狀態碼 是請求任何網頁時,web 伺服器返回的特殊值。如果請求了不存在的網頁,則會返回狀態碼 404 Not Found

  1. let http404錯誤 = (404, "Not Found")
  2. // http404錯誤 的型別為 (Int, String),其值為 (404, "Not Found")

(404, "Not Found") 元組將一個 Int 值與一個 String 值組合起來,表示 HTTP 狀態碼的兩個值:一個數字和一個供人類閱讀的描述。它可以這樣描述:“型別為 (Int, String) 的元組”。

你可以將型別任意排列來建立元組,它們可以包含任意多種不同的型別。只要你願意,建立型別為 (Int, Int, Int)(String, Bool) 的元組也不會有問題,只要這種排列對你有意義。

元組的內容可以還原decompose)為獨立的常量或變數,然後便可照常訪問:

  1. let (狀態碼, 狀態訊息) = http404錯誤
  2. println("狀態碼為 \(狀態碼)")
  3. // 輸出 "狀態碼為 404"
  4. println("狀態訊息為 \(狀態訊息)")
  5. // 輸出 "狀態訊息為 Not Found"

如果你只需要元組的一部分值,可以在還原元組時用下劃線(_)忽略掉其他部分:

  1. let (只留狀態碼, _) = http404錯誤
  2. println("狀態碼為 \(只留狀態碼)")
  3. // 輸出 "狀態碼為 404"

還可以通過以 0 開頭的索引號訪問元組的各個元素值:

  1. println("狀態碼為 \(http404錯誤.0)")
  2. // 輸出 "狀態碼為 404"
  3. println("狀態訊息為 \(http404錯誤.1)")
  4. // 輸出 "狀態訊息為 Not Found"

還可以在定義元組時為各元素命名:

  1. let http200狀態 = (狀態碼: 200, 描述文字: "OK")

為元組各元素命名後,便可以通過元素名稱訪問各元素的值了:

  1. println("狀態碼為 \(http200狀態.狀態碼)")
  2. // 輸出 "狀態碼為 200"
  3. println("狀態訊息為 \(http200狀態.描述文字)")
  4. // 輸出 "狀態訊息為 OK"

元組在作為函式返回值時尤其有用。一個獲取網頁內容的函式可能會返回 (Int, String) 元組型別,來描述網頁裝取是成功還是失敗。函式返回兩個型別完全不同的值描述結果,所能提供的資訊比只能返回固定型別的單個值要有用得多。詳情請參見 返回多個返回值的函式

元組對臨時組合相關的多個值非常有用。它們並不適合用來建立複雜的資料結構。如果你的資料結構的生命期超過臨時使用的範疇,請將它作為類或結構建模,而不是以元組儲存。詳情請見 類與結構

可選量

在值可能不存在時使用可選量optional)。可選量是指:

  • 存在一個值,這個值等於 x

或者

  • 不存在任何值

注:可選量的概念在 C 和 Objective-C 中並不存在。Objective-C 中最相近的是,一個以物件為返回值的方法,可以返回 nil,表示“不存在有效的物件”。不過,該規則只對物件有效——對於結構體、基本的 C 型別或列舉值均不起作用。對於這些型別,Objective-C 語言的方法通常會返回一個特殊值(如 NSNotFound)來表示值不存在。這種策略假定該方法的呼叫方知道要測試返回值是否等於某個特殊值,並且記得要作此檢查。Swift 的可選量允許表示任何型別不存在值,無需定義特殊常量。

舉例說明。Swift 的 String 型別有一個名為 toInt 的方法,可嘗試將 String 值轉為 Int 值。然而,不是所有字串都可以轉換為整數。字串 "123" 可以轉換為數值 123,而字串 "hello, world" 卻顯然沒有對應的數值。

下面的例子會利用 toInt 方法,嘗試將 String 轉換為 Int

  1. let 可能是數字 = "123"
  2. let 轉換得到的數字 = 可能是數字.toInt()
  3. // 轉換得到的數字 被推斷為 "Int?" 型別,即 "可選的 Int"

由於 toInt 方法可能轉換失敗,因此它會返回一個 可選的 Int 型,而不是 Int 型。可選的 Int 記作 Int?,而不是 Int。其中的問號表示該型別包含的值是可選的,即 Int? 可能包含某個 Int 型別的值,也可能不含任何值。(但不能包含其他型別的值,如 Bool 值或 String 值。不是 Int 就是不存在。)

if 語句與強制拆包

可以使用 if 語句測試可選量是否包含值。如果存在,則求值結果為 true;否則為 false

一旦確認可選量的確包含值,便可以通過在變數名末尾新增感嘆號(!)訪問其內部的值。感嘆號明確表達:“我知道這個可選量的確存在值;請使用那個值。”這種操作稱為對可選值進行強制拆包force-unwrap):

  1. if 轉換得到的數字 {
  2.     println("\(可能是數字) 的整數值為 \(轉換得到的數字!)")
  3. } else {
  4.     println("\(可能是數字) 無法轉換為整數")
  5. }
  6. // 輸出 "123 的整數值為 123"

關於 if 語句的更多資訊,請見 流程控制

注:嘗試用 ! 訪問不存在的可選值時會導致執行時錯誤。在用 ! 強制拆包之前,務必確保可選量的確包含非 nil 的值。

可選值繫結

可以通過可選值繫結optional binding)測試可選量是否包含一個值,如果存在,則將該值以臨時常量或變數的形式拆包使用。可選值繫結可以與 ifwhile 語句結合使用,這樣只需要一步就可以檢查是否存在值、提取該值、並存放到常量或變數中。關於 ifwhile 語句的更多情況在 流程控制 一章中講解。

if 語句為例,可選值繫結可以這樣書寫:

  1. if let 常量名稱 = 某可選值 {
  2.     一系列語句
  3. }

上文中 可能是數字 一例,可以改寫用可選值繫結代替強制拆包:

  1. if let 實際值 = 可能是數字.toInt() {
  2.     println("\(可能是數字) 的整數值為 \(實際值)")
  3. } else {
  4.     println("\(可能是數字) 無法轉換為整數")
  5. }
  6. // 輸出 "123 的整數值為 123"

可以這樣理解:

“如果 可能是數字.toInt 返回的 可選的 Int 包含一個值,則新建一個名為 實際值 的常量,並將其值設為可選量中包含的值。”

如果轉換成功,常量 實際值 將可供 if 語句的第一段分支使用。該常量已經以可選量內部的值初始化,因此不再需要用字尾 ! 訪問其值。本例中,實際值 被直接用來輸出轉換結果。

常量與變數均可用於可選值繫結。如果需要在第一個分支中修改 實際值 的值,可以改寫為 if var 實際值,這樣可選量的值將作為變數而非常量拆包。

nil

要將可選變數設為值不存在的狀態,可以給它賦特殊值 nil

  1. var 伺服器響應碼: Int? = 404
  2. // 伺服器響應碼 包含一個實際存在的 Int 值:404
  3. 伺服器響應碼 = nil
  4. // 伺服器響應碼 現在不含任何值

注:nil 不能用於非可選量。如果程式碼中的常量或變數需要適配值不存在的特殊情況,務必將它宣告為恰當的可選型別。

如果定義的可選量時不提供預設值,該常量或變數將自動設為 nil

  1. var 問卷回答: String?
  2. // 問卷回答 被自動設為 nil

注:Swift 的 nil 與 Objective-C 的 nil 不同。Objective-C 的 nil 是指向不存在物件的指標。而 Swift 的 nil 不是指標——它代表特定型別的值不存在。任何型別的可選量都能賦值為 nil,而不僅限於物件型別。

可選量的隱式拆包

如上所述,可選量指允許“值不存在”的常量或變數。可選量可以通過 if 語句測試是否存在值,也可以通過可選值繫結按條件拆包,並在值存在的情況下才訪問可選量的值。

有時根據程式結構可以推斷,可選量在首次賦值後,必然存在值。這些情況下,可以不必每次訪問時都檢測並提取可選量的值,因為可以安全地認為那時一定存在值。

這些可選量可定義為隱式拆包的可選量(implicitly unwrapped optional)。隱式拆包的可選量的宣告格式為,在希望標為可選的型別名稱後面,用感嘆號 (String!) 代替問號 (String?)。

隱式拆包的可選量在可選量首次定義後即確認存在值,在此之後任何時刻都肯定存在的時候有用。Swift 中主要應用在類初始化,詳見 外部引用與隱式拆包的可選屬性

隱式拆包的可選量在實現級別就是普通的可選量,但能夠像非可選量那樣使用,無需在每次訪問時顯式拆包。下例顯示了 可選的 String隱式拆包的可選 String 之間的行為差異:

  1. let 可能是字串: String? = "可選的 String。"
  2. println(可能是字串!) // 訪問其值時需要新增感嘆號
  3. // 輸出 "可選的 String。"
  4. let 假定是字串: String! = "隱式拆包的可選 String。"
  5. println(假定是字串) // 訪問其值時無需感嘆號
  6. // 輸出 "隱式拆包的可選 String。"

可以認為,隱式拆包的可選量即授予可選量在被使用時自動拆包的許可權。不必每次使用可選量時都在名稱後面新增感嘆號,只需在定義時在型別後面加上感嘆號即可。

注:如果在隱式拆包的可選量存在值之前就嘗試訪問,會觸發執行時錯誤。結果與在普通可選量尚未賦值時直接加感嘆號引用相同。

隱式拆包的可選量也可以當作普通可選量對待,檢查是否存在值:

  1. if 假定是字串 {
  2.     println(假定是字串)
  3. }
  4. // 輸出 "隱式拆包的可選 String。"

隱式拆包的可選量同樣可以結合可選值繫結使用,單條語句完成檢查值並拆包的工作:

  1. if let 肯定是字串 = 假定是字串 {
  2.     println(肯定是字串)
  3. }
  4. // 輸出 "隱式拆包的可選 String。"

注:當變數在後續過程中可能變為 nil 時,不應使用隱式拆包的可選量。如果在變數宣告週期內都需要檢查 nil 值,請務必使用普通的可選型別量。

斷言

可選量允許檢查值的存在與否,並允許程式碼能夠適配不存在值的情況。但也有時候,如果值不存在,或不滿足特定條件,程式碼便不可能繼續執行下去。對於這些情況,需要在程式碼中觸發斷言assertion譯註:“觸發”指斷言不成立來終止執行,併為找出值不存在或無效的原因創造機會。

藉助斷言輔助除錯

斷言是一種執行時檢查,確認一定為 true 的邏輯條件是否成立。即,斷言“宣告”某條件一定成立。使用斷言,可確保在進一步執行後續程式碼之前,確保必要條件確實已滿足。如果條件的求值結果為 true,程式碼將照常繼續執行;如果條件的求值結果為 false,則程式碼不再繼續執行,應用程式隨之終止。

如果是在除錯環境執行時觸發斷言(如在 Xcode 中構建並執行應用程式),你將確切地知道異常情況出現的位置,並能在程式執行到該斷言位置的狀態下除錯程式。斷言還允許提供一段合適的除錯訊息,對該斷言加以說明。

斷言可通過呼叫全域性函式 assert 來實現。將需要求值的表示式(結果為 truefalse)傳遞給 assert 函式,如果條件求值結果為 false,則應顯示錯誤訊息:

  1. let age = -3
  2. assert(age >= 0, "人的年齡不可能小於零")
  3. // 斷言觸發錯誤,因為 age >= 0 不成立

本例中,程式碼僅在 age >= 0 的求值結果為 true 時才繼續執行,即,age 的值為非負數才繼續。如果 age 的值為負數,即上述程式碼中的情況,則 age >= 0 的求值結果為 false,於是斷言觸發,應用程式終止。

斷言的訊息內容不能使用字串內插。如果需要,可省略斷言訊息,如下例所示:

  1. assert(age >= 0)

何時應使用斷言

僅當條件可能為假、但必須一定為真程式碼才能繼續執行時,才應使用斷言。適合運用斷言檢查的場景包括:

  • 向自定義下標實現傳遞了整數下標索引,但該索引號可能太小或太大。
  • 值傳遞給了函式,但如果值無效,函式無法完成其任務。
  • 可選值當前為 nil,但後續程式碼要求值非 nil 方可成功執行。

參見 下標函式

注:斷言可使應用程式終止。斷言不適合設計不太可能出現無效條件的場景。儘管如此,在可能出現無效條件的情況下,斷言仍不失為在開發過程中確保這些問題在釋出以前得到關注與處理的有效途徑。

相關文章