Swift學習(2)- 基礎部分(程式碼完善版)
Swift
包含了C
與Objective-C
所有的基本資料型別,Int
代表整數值;Double
與Float
代表浮點數值;Bool
是布林型別;String
則是文字型資料。Swift
還提供了三種基本的集合型別,分別是Array
、Set
與Dictionary
。
就像C
語言一樣,Swift
使用變數來儲存資料並透過變數名稱來關聯值。在Swift
中,廣泛地鼓勵使用不可變的變數,也就是常數,這比C
語言中的常數更強大。在Swift
中,如果某個值不需要變動,使用常數可以讓程式碼更安全,也能更清楚地表達意圖。
除了我們熟悉的型別,Swift
還新增了Objective-C
所沒有的高階資料型別,例如元組(Tuple)。元組可以讓使用者建立或傳遞一組資料,例如作為函式的回傳值時,可以用一個元組回傳多個值。
Swift
還新增了一個可選型別(Optional),用來處理值可能不存在的情況。可選型別的意思是「這裡有一個值,且它等於x」或「這裡沒有值」。可選型別有點像在Objective-C
中使用nil
,但它可以用在任何型別上,不僅僅是類別。可選型別比Objective-C
中的nil
指標更安全且更具表現力,也是Swift
許多強大功能的重要基礎。
Swift
是一個型別安全的語言,這代表Swift
可以讓使用者明確知道值的型別。如果程式需要一個String
參數,型別安全會阻止你不小心傳入一個Int
參數。同樣地,如果程式需要的是String
參數,型別安全也會阻止你意外傳入一個可選的String
。型別安全可以幫助開發者在開發階段及早發現並修正錯誤。
常數與變數
常數與變數將一個名稱(如testVal)與一個指定型別的值(如「Jensen」)關聯起來。常數的值一旦設定就不能再改變,而變數的值可以隨時更改。
宣告常數與變數
常數與變數必須在使用前宣告,使用let
來宣告常數,使用var
來宣告變數。
let maximumNumberOfLoginAttempts = 10 // 常數,記錄可登入嘗試的最大次數
var currentLoginAttempt = 0 // 變數,記錄目前登入嘗試次數
上述兩行程式碼可以理解為:「宣告一個名稱為maximumNumberOfLoginAttempts的新常數,並給它一個值10。然後,宣告一個名稱為currentLoginAttempt的變數並將它的值初始化為0。」
你可以在同一行宣告多個常數或多個變數,使用逗號分隔。
var x = 0.0, y = 0.0, z = 0.0
注意
如果程式中有不需要變動的值,請使用let關鍵字將其宣告為常數。只有需要變動的值才宣告為變數。
型別註解
宣告常數或變數時可以加上型別註解(Type Annotation),說明常數或變數要儲存的值的型別。若要加上型別註解,需要在常數或變數名稱後面加上一個冒號和空格,然後加上型別名稱。
var welcomeMessage: String // 給welcomeMessage變數加上型別註解,表示這個變數可以儲存String型別的值
// 上述宣告中的冒號代表「是...型別」,welcomeMessage變數現在可以被設定為任何字串。
welcomeMessage = "Hello!"
甚至可以在同一行定義多個相同型別的變數,用逗號分隔,並在最後一個變數名稱後加上型別註解。
var red, green, yellow: Double
常數與變數的命名
常數與變數名稱幾乎可以包含所有字元,包括Unicode
字元。
let π = 3.141592654
let 你好 = "你好世界!"
let 🐮🍺 = "666"
但也有例外,常數與變數名稱不能包含數學符號、箭頭、保留(或非法)的Unicode
碼位、連線與定位字元。也不能以數字開頭,但可以在名稱的其他地方包含數字。
一旦給常數或變數加上型別註解,就不能用相同名稱再次宣告,或改變其儲存值的型別。同時,Swift
也不允許變數與變數之間互轉型別。
注意
如果需要使用與Swift保留關鍵字相同的名稱作為常數或變數名稱,可以用反引號(`)將關鍵字包起來作為名稱。不過,開發者應避免使用保留關鍵字作為名稱,除非真的別無選擇。
可以更改現有變數的值為其他相同型別的值。
var friendlyWelcome = "Hello!"
friendlyWelcome = "Benjour!" // friendlyWelcome現在是「Bonjour!」
friendlyWelcome = "你好!" // friendlyWelcome現在是「歡迎!」
與變數不同,常數的值一旦確定就不能更改。嘗試這麼做會在編譯時出錯。
let languageName = "Swift"
//languageName = "C++" // 取消這行註解編譯器會報錯:Cannot assign to value: 'languageName' is a 'let' constant
輸出常數與變數
print(friendlyWelcome) // 輸出:你好!
---
output: 你好!
separator
與terminator
參數有預設值,因此呼叫這個函式時可以忽略它們。
預設情況下,輸出函式會在結尾自動加上換行符。如果不想換行,可以傳遞空字串給terminator
參數。
print("Line1", terminator: "")
print("Line2")
---
output: Line1Line2
Swift
用字串插值(String interpolation)的方式,將常數或變數名稱作為佔位符插入長字串中。Swift
會用目前常數或變數的值取代這些佔位符。將常數或變數名稱放入小括號中,並在前面加上反斜線。
print("The current login attempt is \(currentLoginAttempt), you have \(maximumNumberOfLoginAttempts) login attempts in total.")
---
output: The current login attempt is 0, you have 10 login attempts in total.
註解
Swift
中的註解與C
語言的註解非常類似,單行註解以兩個斜線//
作為起始。
也可以寫多行註解,起始標記為/*
,結束標記為*/
。
/* 這是一個註解
但這是多行註解 */
與C
語言不同的是,Swift
的多行註解可以巢狀在其他多行註解之中。
/* 這是外層註解的第一行
/* 這是內層註解 */
這是外層註解的最後一行 */
利用巢狀多行註解,可以快速方便地註解掉一大段程式碼,即使這段程式碼中已經有多行註解區塊。
分號
與大多數其他程式語言不同,Swift
不強制要求開發者在每條語句結尾加上分號;
。當然,開發者也可以依照習慣加分號,但有一種情況必須加分號,就是在同一行寫多條獨立語句時。
let pig = "🐷"; print(pig)
---
output: 🐷
整數
整數就是沒有小數部分的數字,例如43和-23。Swift
提供8、16、32和64位元的有號與無號整數型別。這些型別的命名方式與C
語言類似,例如8位元無號整數型別是UInt8
,32位元有號整數型別是Int32
。就像Swift
的其他型別一樣,整數型別採用大寫命名。
整數範圍
可以透過不同整數型別的min
與max
屬性來取得對應型別的最小值與最大值。
let minValue = UInt8.min
let maxValue = UInt8.max
print("UInt8型別的最小值是:\(minValue)\nUInt8型別的最大值是:\(maxValue)")
---
output: UInt8型別的最小值是:0
UInt8型別的最大值是:255
min
與max
回傳值的型別就是對應的整數型別,如上例UInt8
,回傳值型別也是UInt8
。
Int
一般來說,不需要特別指定整數的長度。Swift
提供一個特殊的整數型別Int
,長度與目前平台的原生字長相同:
在32位元平台上,Int與Int32長度相同
在64位元平台上,Int與Int64長度相同
UInt
與Int
型別一樣,Swift
也提供一個特殊的無號型別UInt
,長度與目前平台的原生字長相同:
在32位元平台上,UInt與UInt32長度相同
在64位元平台上,UInt與UInt64長度相同
注意
除非真的需要儲存與平台原生字長相同的無號整數,否則盡量不要使用UInt。建議統一使用Int,這樣可以提升程式碼的可重用性,避免不同型別數字之間的轉換,也方便型別推斷。
浮點數
浮點數就是有小數部分的數字,例如3.141592654、0.1和-98.09等。
浮點型別比整數型別能表示更大的範圍,可以儲存比Int型別更大或更小的數字。Swift
提供兩種有號浮點數型別:
Double代表64位元浮點數,當需要儲存很大或高精度的浮點數時請用此型別
Float代表32位元浮點數,若精度要求不高可用此型別
注意
Double的精確度較高,至少有15位小數,而Float只有6位小數。選擇哪種型別取決於你的程式需要處理的數值範圍,在兩者都適用時會優先選擇Double。
型別安全與型別推斷
Swift
是一個型別安全(Type Safe)的語言。型別安全的語言可以讓開發者明確知道程式要處理的值的型別。
由於Swift
是型別安全的,所以它在編譯程式時會進行型別檢查(Type Checks),並將不相容的型別標記為錯誤。這能讓開發者及早發現錯誤。
當要處理不同型別的值時,型別檢查可以幫助避免錯誤。但這並不代表每次宣告常數與變數時都要明確指定型別。如果沒有明確指定型別,Swift
會使用型別推斷(Type Inference)來選擇合適的型別。原理很簡單,只要檢查賦值即可。
當宣告常數或變數並給初值時,型別推斷非常有用。只要在宣告時給它們一個字面值(Literal Value),就會觸發型別推斷。(字面值就是在程式中直接出現的值,例如0.1和3.141592654等)
let meaningOfLife = 42 // meaningOfLife會被型別推斷為Int型別
let pi = 3.141592654 // pi會被型別推斷為Double型別,推斷浮點數型別時,Swift總是選擇Double而非Float
如果運算式中同時出現整數與浮點數,會被推斷為Double
型別。
let anotherPi = 3 + 0.141592654 // 原始值3沒有明確宣告型別,後面又有浮點字面值,所以運算式被推斷為Double型別
數值型字面值
整數字面值可以寫成:
十進位數字,無前綴
二進位數字,前綴為0b;八進位數字,前綴為0o;十六進位數字,前綴為0x
以下所有整數字面值的十進位值都是17:
let decimalInteger = 17
let binaryInteger = 0b10001 // 二進位的17
let octalInteger = 0o21 // 八進位的17
let hexadecimalInteger = 0x11 // 十六進位的17
小數點兩邊必須至少有一個十進位數字或十六進位數字。十進位浮點數也可以有可選的指數(Exponent),用大寫或小寫的e
指定;十六進位浮點數必須有指數,用大寫或小寫的p
指定。
十進位:1.25e2代表1.2510^2,等於125.0;八進位:0xFp-2代表152^-2,等於3.75
以下這些浮點字面值都等於十進位的12.1875。
let decimalDouble = 12.1875
let exponentDouble = 1.21875e1
let hexadecimalDouble = 0xC.3p0
數值型字面值可以包含額外的格式來增進可讀性。整數與浮點數都可以加上額外的零並包含底線,這不會影響字面值的數值。
let paddedDouble = 000123.456
let oneMillion = 1_000_000
let justOverOneMillion = 1_000_000.000_000_1
數值型型別轉換
通常來說,即使程式中的整數常數與變數已知為非負,也請使用Int型別。只有在必要時才使用其他整數型別,例如要處理外部長度明確的資料或為了最佳化效能、記憶體等。使用明確指定長度的型別可以及早發現值溢位,也能暗示正在處理特殊資料。
整數轉換
不同整數型別的變數與常數可以儲存不同範圍的數字。Int8
型別的常數或變數可儲存的數字範圍為-128~127;而UInt8
型別的常數或變數可儲存的數字範圍是0~255。如果數字超出可儲存範圍,編譯時會報錯。
// let cannotBeNegative: UInt8 = -1 // 取消這行註解會報錯:Negative integer '-1' overflows when stored into unsigned type 'UInt8'
// let tooBig: Int8 = Int8.max + 1 // 取消這行註解會報錯:Arithmetic operation '127 + 1' (on type 'Int8') results in an overflow
開發者需要根據情況選擇是否進行數值型型別轉換,這種選擇性使用方式可以預防隱式轉換的錯誤,並讓程式中的型別轉換意圖更明確。
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one) // 這裡用UInt16(one)建立一個新的UInt16數字並用one的值初始化,然後就能讓兩個無號16位元值相加
print("UInt8轉換至UInt16: ", twoThousandAndOne)
---
output: UInt8轉換至UInt16: 2001
SomeType(ofInitialValue)
是呼叫Swift建構子並傳入初始值的預設方法。在語言內部,UInt16
有一個建構子,可以接受一個UInt8
型別的值,所以這個建構子可以用現有的UInt8
來建立一個新的UInt16
。
整數與浮點數轉換
整數與浮點數的轉換必須明確指定型別。
let three = 3
let pointOneFourOneFiveNine = 0.14159
let simplePi = Double(three) + pointOneFourOneFiveNine // pi等於3.14159,所以被型別推斷為Double型別
print("整數與浮點數轉換:", simplePi)
---
output: 整數與浮點數轉換: 3.14159
在上述例子中,常數three
的值被用來建立一個Double
型別的值,否則加號兩邊的數型別不同,無法相加。
浮點數轉換為整數的反向轉換也可以,整數型別可以用Double
或Float
型別來初始化。
let roundPi = Int(simplePi) // roundPi等於3,所以被推斷為Int型別
print("浮點數轉換至整數:", roundPi)
---
output: 浮點數轉換至整數: 3
用這種方式初始化新整數值時,浮點值會被截斷,也就是4.75會變成4,-3.9會變成-3。
注意
結合數字常數與變數不同於結合數字字面值。字面值3可以直接與字面值0.14159相加,因為數字字面值本身沒有明確型別。它們的型別只在編譯器需要求值時才會被推斷。
型別別名
型別別名(Type Aliases)就是給現有型別定義另一個名稱。開發者可以使用typealias
關鍵字來定義型別別名。
當想要給現有型別取一個更有意義的名稱時,型別別名非常有用。
假設現在正在處理特定長度的外部資源資料,定義型別別名後,可以在任何使用原始名稱的地方使用別名。
typealias AudioSample = UInt16
var maxAmplitudeFound = AudioSample.min // maxAmplitudeFound現在是0
print("maxAmplitudeFound現在的值是:", maxAmplitudeFound)
---
output: maxAmplitudeFound現在的值是: 0
上述例子中,AudioSample
被定義為UInt16
的別名,因為是別名,所以AudioSample.min
其實就是UInt16.min
。
布林值
Swift
有一個基本的布林(Boolean)型別,叫做Bool
。布林值指的是邏輯上的值,只能為真或假。Swift
有兩個布林值常數,true
與false
。
let orangesAreOrange = true
let turnipsAreDelicious = false
orangesAreOrange
與turnipsAreDelicious
的型別會被推斷為Bool
,因為它們的初值是布林字面值。就像前面提到的Int
與Double
一樣,如果建立常數/變數時就給予true
或false
,就不需要將其宣告為Bool
型別。
撰寫條件語句,特別是if
條件語句時,布林值就非常有用。
if turnipsAreDelicious {
print("Haha, turnips are delicious!")
} else {
print("Oh shit! Turnips are horrible!")
}
---
output: Oh shit! Turnips are horrible!
如果在需要Bool
型別的地方使用非布林值,Swift
的安全機制會報錯。
let test = 1
//if test {} // 如果取消這行註解,編譯器會報錯:Type 'Int' cannot be used as a boolean; test for '!= 0' instead
同樣地,下例是合法的。
let i = 1
if i == 1 {} // i == 1的結果是Bool型別,所以可以通過Swift的型別檢查
元組
元組(Tuple)將多個值組合成一個複合值。元組內的值可以是任意型別,不必相同。
下例,(404, “Not Found”) 是一個描述HTTP
狀態碼的元組。
let http404Error = (404, "Not Found") // http404Error的型別是(Int, String)
(404, “Not Found”)元組將一個Int
值與一個String
值組合起來,表示HTTP
狀態碼的兩個部分。這個元組可以描述為「一個型別為(Int, String)的元組」。
可以將任意順序的型別組合成一個元組,元組可以包含所有型別。還可以將元組內容分解(Decompose)成單獨的常數與變數,然後就能正常使用它們。
let (statusCode, statusMessage) = http404Error
print("The http status code is \(statusCode).")
print("The http status message is \(statusMessage).")
---
output: The http status code is 404.
The http status message is Not Found.
如果只需要部分元組值,分解時可以將要忽略的部分用底線(_)標記,類似於Python
。
let (httpStatusCode, _) = http404Error
print("The temp http status code is \(httpStatusCode).")
---
output: The temp http status code is 404.
也可以透過下標存取元組中的單一元素,下標從零開始。
print("透過下標存取元組中的第一個元素:", http404Error.0)
print("透過下標存取元組中的第二個元素:\(http404Error.1)")
---
output: 透過下標存取元組中的第一個元素: 404
透過下標存取元組中的第二個元素:Not Found
可以在定義元組時給每個元素命名,然後透過名稱取得這些元素的值。
let http500Status = (statusCode: 500, description: "Server Error")
print("透過元組元素命名方式存取第一個元素:\(http500Status.statusCode)")
print("透過元組元素命名方式存取第二個元素:\(http500Status.description)")
---
output: 透過元組元素命名方式存取第一個元素:500
透過元組元素命名方式存取第二個元素:Server Error
當函式需要回傳多個值時,元組非常有用。
可選型別
使用可選型別(Optionals)來處理值可能不存在的情況。可選型別表示有兩種可能:
要嘛有值,開發者可以選擇存取這個值;要嘛根本沒有值
舉例來說,Swift
的Int
型別有一種建構子,可以將String
值轉換成Int
值。然而,不是所有字串都能轉換成整數。字串「123」可以轉換成數字123,但字串「Hello, world」就不行。
let possibleNumber = "123"
var convertedNumber = Int(possibleNumber)
print("convertedNumber被推斷為型別「Int?」,也就是「optional Int」:", convertedNumber!)
let possibleName = "Jensen"
convertedNumber = Int(possibleName)
print("字串\(possibleName)轉成數字後有問題:", convertedNumber)
---
output: convertedNumber被推斷為型別「Int?」,也就是「optional Int」: 123
字串Jensen轉成數字後有問題: nil
---
warning: Expression implicitly coerced from 'Int?' to 'Any'
由於這個建構子可能會失敗,所以Int(xxx)
回傳的是可選型別(optional)的Int
,而不是一般的Int
。可選的Int
寫作Int?
,問號代表這個值是可選型別,但不能包含其他型別的值,例如Bool
或String
,只能是Int
或什麼都沒有。
nil
可以給可選變數賦值為nil,表示沒有值。
var serverResponseCode: Int? = 404 // serverResponseCode包含一個可選的Int值404
serverResponseCode = nil // serverResponseCode現在沒有值
print("可選值serverResponseCode:", serverResponseCode)
---
output: 可選值serverResponseCode: nil
---
warning: Expression implicitly coerced from 'Int?' to 'Any'
請注意,nil
不能用於非可選的常數與變數。如果程式中有常數或變數需要處理值不存在的情況,請將它們宣告為對應的可選型別。如果宣告一個可選常數或變數但沒有賦值,它們會自動被設定為nil
。
var surveyAnswer: String?
print("沒有賦值的可選常/變數的值為:", surveyAnswer)
---
output: 沒有賦值的可選常/變數的值為: nil
---
warning: Expression implicitly coerced from 'String?' to 'Any'
if語句與強制解析
可以使用if
語句與nil
比較來判斷可選值是否有值,可以用「等於」(==)或「不等於」(!=)來比較。
如果可選型別有值,它就不等於nil
。
if convertedNumber != nil {
print("convertedNumber contains some integer value.")
} else {
print("convertedNumber is nil.")
}
---
output: convertedNumber is nil.
當確定可選型別確實有值後,可以在可選名稱後加上驚嘆號!
來取得值。這個動作稱為可選值的強制解析(Forced Unwrapping)。
let optionalInteger: Int? = 10086
if optionalInteger != nil {
print("The optional integer value is \(optionalInteger!)") // 取得不存在的可選值會導致執行錯誤,使用!強制解析前,一定要確定可選有非nil的值。
} else {
print("The optional value is Nil.")
}
---
output: The optional integer value is 10086
可選綁定
可選綁定(Optional Binding)用來判斷可選型別是否有值,如果有就把值賦給一個暫時的常數或變數。可選綁定可以用在if
與while
語句中。
if let actualNumber = Int(possibleNumber) {
print("'\(possibleNumber)' has an integer value of \(actualNumber).")
} else {
print("'\(possibleNumber)' could not be converted to an Integer.")
}
---
output: '123' has an integer value of 123.
上述程式碼可以理解為「如果Int(possibleNumber)回傳的可選Int有值,建立一個叫actualNumber的新常數並將可選Int的值賦給它。actualNumber已經被可選型別的值初始化,所以不需要再用!來取得它的值」。
可以在同一個if
語句中包含多個可選綁定或多個布林條件,只要用逗號分隔即可。只要有一個可選綁定的值為nil
或任一布林條件為false
,整個if
條件就為false
。
if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber && secondNumber < 100 {
print("\(firstNumber) > \(secondNumber) < 100")
}
// 等同於下面的程式碼
if let firstNumber = Int("4") {
if let secondNumber = Int("42") {
if firstNumber < secondNumber && secondNumber < 100 {
print("\(firstNumber) > \(secondNumber) < 100")
}
}
}
---
output: 4 > 42 < 100
4 > 42 < 100
注意
在if條件語句中使用常數與變數建立可選綁定,只能在if語句的主體(body)中取得值。相對地,在guard語句中建立可選綁定,只能在guard語句外且在語句之後取得值。
隱式解析可選型別
有時候在程式架構中,第一次被賦值後,可以確定一個可選型別之後一定會有值。在這種情況下,每次都判斷與解析可選值會很沒有效率。這種型別的可選狀態稱為隱式解析可選型別(Implicitly Unwrapped Optionals)。將想要用作可選的型別後面的問號String?
改成驚嘆號String!
來宣告一個隱式解析可選型別。與其在使用時將驚嘆號放在可選型別名稱後面,不如在定義時就直接將驚嘆號放在型別後面。
當可選型別第一次被賦值後就能確定之後一定有值時,隱式解析可選型別非常有用。隱式解析可選型別主要用在Swift
中類別的初始化過程。
一個隱式解析可選型別其實就是一般的可選型別,但可以當作非可選型別來使用,不需要每次都解析可選值。
let possibleString: String? = "An optional String."
let forcedString = possibleString!
print("顯式解析可選型別:", forcedString)
let assumedString: String! = "An implicitly unwrapped optional String."
let implicitString: String = assumedString
print("隱式解析可選型別:", implicitString)
---
output: 顯式解析可選型別: An optional String.
隱式解析可選型別: An implicitly unwrapped optional String.
可以把隱式解析可選型別當作一個可以自動解析的可選型別。當使用一個隱式解析可選值時,Swift
會先把它當作一般可選值。如果不能當作可選型別使用,Swift
會強制解析可選值。在上述程式碼中,可選值assumedString
在賦值給implicitString
前會被強制解析,因為implicitString
本身型別就是非可選型別的String
。
下例optionalString
沒有明確的資料型別,根據型別推斷,就是一般的可選型別。
let optionalString = assumedString
如果在隱式解析可選型別沒有值時嘗試取值,會觸發錯誤。與在沒有值的一般可選型別後加驚嘆號一樣。
可以將隱式解析可選型別當作一般可選型別來判斷是否有值。
if assumedString != nil {
print("隱式解析可選型別取值:", assumedString!)
}
---
output: 隱式解析可選型別取值: An implicitly unwrapped optional String.
也可以在可選綁定中使用隱式解析可選型別來檢查並解析其值。
if let definiteString = assumedString {
print("隱式解析可選型別取值:", definiteString)
}
---
output: 隱式解析可選型別取值: An implicitly unwrapped optional String.
注意
如果一個變數之後可能變成nil,請不要使用隱式解析可選型別。如果需要在變數生命週期中判斷是否為nil,請使用一般可選型別。
錯誤處理
可以使用錯誤處理(Error Handling)來應對程式執行中可能遇到的錯誤狀況。
相較於可選型別用值的有無來表達函式的成功與失敗,錯誤處理可以推斷失敗的原因,並傳遞到程式的其他部分。
當一個函式遇到錯誤狀況時,可以拋出錯誤。呼叫函式的地方可以捕捉錯誤訊息並合理處理。
func canThrowAnError() throws {
// 這個函式內部可能會拋出錯誤
}
一個函式可以在宣告時加上throws
關鍵字來拋出錯誤訊息。當函式可能拋出錯誤時,應在運算式前加上try
關鍵字。
do {
try canThrowAnError()
// 沒有錯誤訊息拋出則執行後續程式
} catch {
// 有錯誤訊息拋出則執行這裡
}
一個do
語句會建立新的作用域,使錯誤能被傳遞到一個或多個catch
子句。下例為錯誤處理如何應對不同錯誤狀況的範例。
func makeASandwich() throws {
// function body
}
enum SandwichError: Error {
case outOfCleanDishes
case missingIngredients(ingredients: String)
}
do {
try makeASandwich()
// eatASandwich()
} catch SandwichError.outOfCleanDishes {
// washDishes()
} catch SandwichError.missingIngredients(let ingredients) {
// 如果匹配missingIngredients的錯誤被拋出,buyGroceries(_:)函式會被呼叫,並使用catch捕捉到的關聯值[ingredients]作為參數。
// buyGroceries(ingredients)
}
---
warning: Immutable value 'ingredients' was never used; consider replacing with '_' or removing it
上述例子中,makeASandwich()
(做三明治)函式會拋出錯誤訊息,如果沒有乾淨的盤子或某個原料缺失。因為makeASandwich()
會拋出錯誤,函式呼叫要包在try
運算式中。將函式包在do
語句中,任何被拋出的錯誤都會被傳遞到提供的catch
子句。
斷言與前置條件
斷言與前置條件是在執行時進行的檢查。可以用來檢查在執行後續程式前,某個必要條件是否被滿足。如果斷言或前置條件中的布林條件評估結果為true
(真),則程式照常執行。如果評估結果為false
(假),代表程式目前狀態無效,程式執行會終止,應用程式中止。
斷言幫助開發者在開發階段發現錯誤與不正確的假設,前置條件則幫助開發者在生產環境偵測問題。與錯誤處理不同,斷言與前置條件不是用來處理可恢復或可預期的錯誤。因為斷言失敗代表程式處於無效狀態,無法捕捉失敗的斷言。
注意
使用斷言與前置條件不是避免程式出現無效狀態的寫法。但如果真的出現無效狀態,斷言與前置條件可以強制檢查資料與程式狀態,使程式可預期地中止(不是系統強制、被動中止),並幫助問題更容易除錯。一旦偵測到無效狀態,執行就會中止,避免無效狀態造成系統進一步損害。
斷言與前置條件的差異在於它們何時進行狀態檢查:斷言只在除錯環境執行,前置條件則在除錯與生產環境都會執行。在生產環境中,斷言條件不會被評估。這代表可以在開發階段大量使用斷言,但這些斷言在生產環境不會有任何影響。
使用斷言進行除錯
可以呼叫Swift
標準函式assert(_:_:file:line:)
來寫斷言。傳入一個結果為true
或false
的運算式與一段訊息,當運算式結果為false
時這段訊息會顯示出來。
let age = -3
assert(age >= 0, "A person's age cannot be less than zero.") // 因為age < 0,所以斷言會被觸發
上述程式碼中,如果age
的值是負數,就像程式碼中那樣,age >= 0
為false
,斷言被觸發,應用程式終止。
如果不需要斷言訊息,可以像下面這樣省略。
assert(age >= 0)
如果程式已經檢查過條件,可以使用assertionFailure(_:file:line:)
函式來表示斷言失敗。
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.")
}
強制執行前置條件
當某個條件可能為假,但繼續執行程式要求條件必須為真時,需要使用前置條件。例如用前置條件檢查是否陣列越界,或檢查是否傳入正確參數給函式。
可以使用全域precondition(_:_:file:line:)
函式來寫前置條件。傳入一個結果為true
或false
的運算式與一段訊息,當運算式結果為false
時這段訊息會顯示出來。
var index = 0
precondition(index > 0, "Index must be greater than zero.") // 索引必須從1開始
可以呼叫preconditionFailure(_:file:line:)
方法來表示出現錯誤,例如,switch
進入default
分支,但所有有效值應該被其他分支處理。
注意
如果用unchecked模式編譯程式,前置條件將不會檢查。編譯器假設所有前置條件都為true(真),會最佳化你的程式碼。但fatalError(_:file:line)函式無論如何都會中斷執行,不論最佳化設定如何。
可以在設計原型與早期開發階段使用fatalError(_:file:line)函式,此階段只有方法宣告但沒有實作,可以在方法體中寫fatalError(“Unimplemented”)作為實作。因為fatalError不會像斷言與前置條件那樣被最佳化掉,可以確保程式執行到未實作的方法時會中斷。