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: Line1Line2Swift用字串插值(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型別的最大值是:255min與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: 2001SomeType(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 = falseorangesAreOrange與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不會像斷言與前置條件那樣被最佳化掉,可以確保程式執行到未實作的方法時會中斷。