這篇筆記也是同步 Swift 6 官方教程中的第二篇 《The Basics》,這篇博客中的大部分內容在第一篇中已經涉及,這篇可以被認為是基礎類型的的補充篇,多了很多說明信息。
- 官方教學文檔 《The Basics》:
Swift 提供了許多基本數據類型,包括用于整數的 Int
、用于浮點值的 Double
、用于布爾值的 Bool
以及用于文本的 String
。Swift 還提供了三種主要集合類型(數組、集合、字典)。
Swift 使用 變量 通過標識名稱來存儲和引用值,同時廣泛使用 值不可更改的變量 即常量,目的是讓代碼更安全、意圖更清晰。
除了常見的類型外,Swift 還引入了元組等高級類型。通過元組可以創建和傳遞一組值,使用元組將函數中的多個值作為單個復合值返回。
可選類型 用來處理值的缺失的情況,可選類型表示“存在一個值”或“根本沒有值”。可選值確保代碼在使用值之前始終檢查該值是否缺失,并且保證非可選值永遠不會缺失。
Swift 是一門 安全的語言,這意味著它有以下幾個特性:
- 可以在開發過程中盡早發現和修復幾類錯誤,并保證某些類型的錯誤不會發生;
- 能夠清楚地了解代碼所處理值的類型,如果部分代碼需要字符串,可以防止錯誤地將整數傳遞給它;
- 確保只能使用有效數據,而不是未初始化的內存或未初始化的對象;
Swift 在構建代碼時執行大部分安全檢查,并且在某些情況下會在代碼運行時執行額外的檢查。
1. Constants and Variables 常量與變量
常量和變量將一個名稱(例如 maximumNumberOfLoginAttempts
或 WelcomeMessage
)與特定類型的值(例如數字 10 或字符串“Hello”)關聯起來。常量的值一旦設置就無法更改,而變量的值可以在將來設置為其他值。
1.1 Declaring Constants and Variables 常量與變量聲明
常量和變量必須在使用前聲明。使用 let
關鍵字聲明常量,使用 var
關鍵字聲明變量。以下示例展示了如何使用常量和變量來跟蹤用戶登錄嘗試次數:
let maximumNumberOfLoginAttempts = 10
var currentLoginAttempt = 0
這段代碼可以理解為:聲明一個名為 maximumNumberOfLoginAttempts
的新常量,并賦值為 10
;聲明一個名為 currentLoginAttempt
的新變量,并賦值為 0
。
在此示例中,允許的最大登錄嘗試次數被聲明為常量,因為最大值永遠不會改變。當前登錄嘗試計數器被聲明為變量,因為該值必須在每次登錄嘗試失敗后遞增。
如果代碼中存儲的值不會改變,應始終使用 let
關鍵字將其聲明為常量。變量僅用于存儲會改變的值。
聲明常量或變量時,可以像上面的示例一樣在聲明過程中賦值,也可以在程序的后續階段賦值,只要確保在第一次使用之前它已經有一個值即可。
var enviroment = "development"
let maximumNumberOfLoginAttempts: Intif enviroment == "development"{maximumNumberOfLoginAttempts = 100
}else{maximumNumberOfLoginAttempts = 10
}
在此示例中,最大登錄嘗試次數是一個常量;在開發環境中其值為 100;在其他環境中其值為 10。if
語句的兩個分支都使用某個值初始化 maximumNumberOfLoginAttempts
,從而確保該常量始終獲得一個值。
在同一行中聲明多個常量或變量,并用逗號分隔:
var x = 0.0, y = 0.0, z = 0.0
1.2 Type Annotations 類型注解
聲明常量或變量時可以提供類型注解,以明確該常量或變量可以存儲的值類型。編寫類型注解的方法是,在常量或變量名稱后添加一個冒號 :
,然后跟一個空格,最后跟要使用的類型名稱。
此示例為名為welcomeMessage
的變量提供了一個類型注解,以指示該變量可以存儲字符串值:
var welcomeMessage: String
上面的代碼可以理解為:聲明一個名為 welcomeMessage
的變量,其類型為String。
將 welcomeMessage
變量設置為任何字符串值而不會出現錯誤:
var welcomeMessage: StringwelcomeMessage = "Hello"
可以在同一行上定義多個相同類型的相關變量,用逗號分隔,并在最終變量名后添加單個類型注釋:
var red, green, blue: Double
1.3 Naming Constants and Variables 變量與常量命名
常量和變量名可以包含幾乎任何字符,包括 Unicode 字符:
let π = 3.14159
let 你好 = "你好世界"
let 🐶🐮 = "dogcow"
但常量和變量名不能包含空格字符、數學符號、箭頭、私有的 Unicode 標量值或線條和框繪制字符。它們也不能以數字開頭,但數字可以出現在名稱的其他位置。
一旦聲明了某種類型的常量或變量,就不能再用相同的名稱聲明它,也不能將其更改為存儲不同類型的值。也不能將常量更改為變量,也不能將變量更改為常量。
可以將現有變量的值更改為兼容類型的其他值。在此示例中,friendlyWelcome
的值從“Hello!”更改為“Bonjour!”:
var friendlyWelcome = "Hello"
friendlyWelcome = "Bonjour"
與變量不同,常量的值一旦設置就無法更改。嘗試更改代碼時,編譯時會報錯:
let languageName = "Swift"
languageName = "Swift++" // 報錯,常量不可修改
1.4 Printing Constants and Variables 輸出變量與常量
可以使用 print(_:separator:terminator:)
函數打印常量或變量的當前值:
var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"print(friendlyWelcome) // Bonjour!
print(_:separator:terminator:)
函數是一個全局函數,它將一個或多個值打印到適當的輸出。例如,在 Xcode 中,print(_:separator:terminator:)
函數將其輸出打印在 Xcode 的“控制臺”窗格中。分隔符和終止符參數具有默認值,因此可以在調用此函數時省略它們。默認情況下,該函數會通過添加換行符來終止其打印的行。如果想要打印一個不帶換行符的值,需要傳遞一個空字符串作為終止符 -
例如,print(someValue, terminator: "")
。
var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"print(friendlyWelcome, terminator: "")
Swift 使用字符串插值,將常量或變量的名稱作為占位符包含在較長的字符串中,并提示 Swift 將其替換為該常量或變量的當前值。將名稱括在括號中,并在左括號前使用反斜杠進行轉義:
var friendlyWelcome = "Hello!"
friendlyWelcome = "Bonjour!"print("The current value of friendlyWelcome is \(friendlyWelcome)")
// The current value of friendlyWelcome is Bonjour!
2. Comments 注釋
2.1 Single line comments 單行注釋
使用注釋在代碼中添加不可執行的文本,作為注釋或提醒,編譯器在編譯代碼時會忽略注釋。
Swift 中的注釋與 C 語言中的注釋非常相似。單行注釋以兩個正斜杠 //
開頭:
// This is a comment.
2.2 Multi line comments 多行注釋
多行注釋以斜杠加星號 /*
開頭,以星號加斜杠 */
結尾:
/* This is also a comment
but is written over multiple lines. */
2.3 Multi line inside comments 整塊注釋
與 C 語言中的多行注釋不同,Swift 中的多行注釋可以嵌套在其他多行注釋中。編寫嵌套注釋的方法是:先開始一個多行注釋塊,然后在第一個注釋塊內開始第二個多行注釋。然后,第二個注釋塊結束,接著是第一個注釋塊:
/* This is the start of the first multiline comment./* This is the second, nested multiline comment. */
This is the end of the first multiline comment. */
嵌套多行注釋能夠快速輕松地注釋掉大塊代碼,即使代碼已經包含多行注釋,這一點比 C++ 的邏輯方便太多了。
3. Semicolons 分號
Swift 并不要求在代碼中的每個語句后都強制添加分號 ;
即便加上也可以通過編譯。如果想在一行中編寫多個單獨的語句,則必須使用分號:
let cat = "🐱"; print(cat)
// 🐱
4. Integers 整型
整數是不帶小數部分的整數,例如 42 和 -23。整數可以是有符號整數(正數、零或負數),也可以是無符號整數(正數或零)。
Swift 提供 8、16、32 和 64 位的有符號整數和無符號整數。這些整數遵循與 C 語言類似的命名約定,即 8 位無符號整數的類型為 UInt8
,32 位有符號整數的類型為 Int32
。與 Swift 中的所有類型一樣,這些整數類型的名稱均采用大寫字母。
4.1 Integer Bounds 整型范圍
使用 min
和 max
屬性訪問每個整數類型的最小值和最大值:
let minValue = UInt8.min
let maxValue = UInt8.maxprint(minValue, maxValue) // 0 255
這些屬性可以與相同類型的其他值一起在表達式中使用。
4.2 Int 整型
大多數情況下,無需在代碼中指定整數的大小。Swift 提供了一個額外的整數類型 Int
,其大小 與當前平臺的原生字長相同:
- 在 32 位平臺上,
Int
的大小與Int32
相同。 - 在 64 位平臺上,
Int
的大小與Int64
相同。
除非特殊情況,否則應該始終使用 Int
表示整數值,這樣有助于提高代碼的一致性和互操作性。即使在 32 位平臺上,Int
也可以存儲 ? 2 , 147 , 483 , 648 -2,147,483,648 ?2,147,483,648 到 2 , 147 , 483 , 647 2,147,483,647 2,147,483,647 之間的任何值,并且足以容納許多整數范圍。
4.3 UInt 無符號整型
Swift 還提供了一個無符號整數類型 UInt
,其大小與當前平臺的原生字長相同:
- 在 32 位平臺上,
UInt
的大小與UInt32
的大小相同。 - 在 64 位平臺上,
UInt
的大小與UInt64
的大小相同。
5. Floating-Point Numbers 浮點型
浮點數是帶有小數部分的數字,例如 3.14159 3.14159 3.14159、 0.1 0.1 0.1 和 ? 273.15 -273.15 ?273.15。浮點類型可以表示比整數類型更廣泛的值,并且可以存儲比 Int
類型更大或更小的數字。Swift 提供了兩種有符號浮點數類型:
Double
表示 64 位浮點數。Float
表示 32 位浮點數。
6. Type Safety and Type Inference 類型安全與類型推斷
Swift 中的 每個值都有類型,可以使用類型注解明確指定類型,或者由 Swift 根據初始值推斷類型。每個提供值的地方,該值的類型都必須與使用它的地方匹配,如果代碼的某個部分需要一個字符串,就不能將其賦值一個整數,這種檢查使 Swift 成為一種類型安全的語言。
類型安全的語言要求明確代碼所處理值的類型,這樣一種類型的值永遠不會被隱式轉換為另一種類型。但是某些類型可以顯式轉換。在編譯時任何不匹配的類型標記為錯誤。
類型檢查并不意味著必須明確聲明的每個常量和變量的類型,如果未指定所需值的類型,Swift 會使用類型推斷來計算出合適的類型,類型推斷使編譯器能夠在編譯代碼時自動推斷特定表達式的類型。
由于類型推斷,Swift 所需的類型聲明比 C 或 Objective-C 等語言少得多,常量和變量仍然需要顯式指定類型。
類型推斷通常是通過在聲明常量或變量時為其賦值(或字面量)來實現的:
let meaningOfLife = 42 // 編譯器推斷為 Int
let pi = 3.14159 // 編譯器推斷為 Double
Swift 在推斷浮點數的類型時始終選擇 Double
而不是 Float
,如果在表達式中組合使用整數和浮點字面量,則會根據上下文推斷出 Double
類型:
let anotherPi = 3 + 0.14159 // 編譯器仍然會推斷為 Double
7. Numeric Literals 數字字面量
整數字面量可以寫成:
- 十進制數,無前綴
- 二進制數,前綴為 0b
- 八進制數,前綴為 0o
- 十六進制數,前綴為 0x
以下整數字面量都以十進制數 17 結尾:
let decimalInteger = 17 // 17 in 十進制
let binaryInteger = 0b10001 // 17 in 二進制
let octalInteger = 0o21 // 17 in 八進制
let hexadecimalInteger = 0x11 // 17 in 十六進制
對于指數為 x x x 的十進制數,基數乘以 10 x 10^{x} 10x:
1.25e2
表示 1.25 x 102,即 125.0;1.25e-2
表示 1.25 x 10?2,即 0.0125;
對于指數為 x x x 的十六進制數,基數乘以 2 x 2^{x} 2x:
0xFp2
表示 15 x 22,即 60.0;0xFp-2
表示 15 x 2?2,即 3.75;
以下所有浮點字面值的十進制值均為 12.1875:
0xFp2
表示 15 x 22, 即60.0;0xFp-2
表示 15 x 2?2,即 3.75;
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
8. Type Conversion 類型轉換
8.1 Numeric Type Conversion 數字類型轉換
代碼中所有通用的整數常量和變量都應使用 Int
類型,在日常情況下使用默認整數類型意味著整數常量和變量可以 立即在代碼中互操作,并且將與整數字面值的推斷類型匹配。
僅在當前明確需要時才使用其他整數類型,例如來自外部數據大小明確,或者出于性能、內存必要優化的考慮。在這些情況下使用明確的類型有助于捕獲意外的值溢出,并隱式地記錄所用數據的性質。
8.2 Integer Conversion 整型轉換
整數常量或變量可以存儲的數字范圍因類型而異。Int8
常量或變量可以存儲 -128 到 127 之間的數字,而 UInt8
常量或變量可以存儲 0 到 255 之間的數字。如果數字無法放入指定大小的整數類型的常量或變量中,則會在代碼編譯時報錯:
let cannotBeNegative: UInt8 = -1 // 報錯,不能承載負數
let tooBig: Int8 = Int8.max + 1 // 報錯,超過了范圍
由于每種數字類型可以存儲不同范圍的值,因此您必須根據具體情況選擇是否進行數字類型轉換。這種選擇方法可以避免隱藏的轉換錯誤,并有助于在代碼中明確類型轉換意圖。
要將一種特定的數字類型轉換為另一種,需要使用現有值初始化一個所需類型的新數字。在下面的示例中,常量 twoThousand
的類型為 UInt16
,而常量 one
的類型為 UInt8
,由于它們不是同一類型,因此不能直接將它們相加。
let twoThousand: UInt16 = 2_000
let one: UInt8 = 1
let twoThousandAndOne = twoThousand + UInt16(one)
8.3 Integer and Floating-Point Conversion 整型與浮點轉換
整數和浮點數類型之間的轉換必須明確:
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNine
浮點數到整數的轉換也必須顯式進行。整數類型可以用 Double
或 Float
值初始化:
let three = 3
let pointOneFourOneFiveNine = 0.14159
let pi = Double(three) + pointOneFourOneFiveNinelet integerPi = Int(pi)
以這種方式初始化新的整數值時,浮點值會被??截斷,如 4.75 會被截斷為 4,-3.9 會被截斷為 -3。
9. Type Aliases 類型別名
類型別名可以為現有類型定義一個替代名稱,使用 typealias
關鍵字定義類型別名。
typealias AudioSample = UInt16
一旦定義了類型別名,就可以在任何可能使用原始名稱的地方使用該別名:
typealias AudioSample = UInt16
var maxAmplitudeFound = AudioSample.minprint(maxAmplitudeFound) // 0
10. Booleans 布爾值
布爾值被稱為邏輯值只能為真或假:true
和 false
:
let orangesAreOrange = true
let turnipsAreDelicious = false
orangesAreOrange
和 turnipsAreDelicious
的類型已被推斷為 Bool,與上面的 Int
和 Double
一樣,如果常量或變量在創建時就設置為 true 或 false,則無需將其聲明為 Bool。
用 Bool 類型控制條件語句:
let turnipsAreDelicious = falseif turnipsAreDelicious {print("Mmm, tasty turnips!")
} else {print("Eww, turnips are horrible.")
}
Swift 的類型安全機制阻止將非布爾值替換為 Bool 值。以下示例會報告編譯時錯誤:
let var = 1
if var{// ... 編譯報錯
}
但是判等條件是可以用的:
let var = 1
if var == 1{// ... 編譯通過
}
11. Tuples 元組
元組將多個值組合成一個復合值。元組中的值可以是任意類型,并且彼此的類型不必相同。
下面的代碼中 (404, "Not Found")
是一個描述 HTTP 狀態代碼的元組,如果您請求的網頁不存在,則會返回狀態代碼 404 Not Found
,這個元組將一個 Int
和一個 String
組合在一起。
let http404Error = (404, "Not Found")
可以將元組的內容分解為單獨的常量或變量并進行訪問:
let http404Error = (404, "Not Found")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 http404Error = (404, "Not Found")let (justTheStatusCode, _) = http404Error
print("The status code is \(justTheStatusCode)") // The status code is 404
也可以使用從零開始的索引號訪問元組中的各個元素值:
let http404Error = (404, "Not Found")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 http404Error = (statusCode: 200, description: "OK")print("The status code is \(http404Error.statusCode)") // The status code is 200
print("The status message is \(http404Error.description)") // The status message is OK
12. Optionals 可選類型
在值可能缺失的情況下,可以使用可選類型 Optionals
。可選類型代表兩種可能性:要么存在指定類型的值,要么根本沒有值。
以下示例使用初始化器嘗試將字符串轉換為 Int:
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)
print(type(of: convertedNumber)) // Optional<Int>
12.1 nil 空值
可以通過為可選變量分配特殊值 nil
來將其設置為無值狀態:
var serverResponseCode: Int? = 404
serverResponseCode = nil
print(serverResponseCode == nil) // true
如果定義可選變量而不提供默認值,則該變量將自動設置為 nil:
var suveryAnswer: String?
print(suveryAnswer == nil) // true
可以使用 if
語句通過將可選值與 nil
進行比較來判斷其是否包含值。如果可選值有值,則 != nil
:
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)if convertedNumber != nil{print("convertedNumber contains some integer value.")
}
// convertedNumber contains some integer value.
不能將 nil
與非可選常量或變量一起使用。如果代碼中的常量或變量需要在某些條件下處理值缺失的情況,應將其聲明為相應類型的可選值。聲明為非可選值的常量或變量則需要確保永遠不會等于 nil
值,否則會引發編譯時錯誤。
這種可選值和非可選值的分離可以 明確標記哪些信息可以缺失,并使編寫處理缺失值的代碼。解包值后,其他使用該值的代碼都無需檢查其是否為 nil
,因此無需在代碼的不同部分重復檢查相同的值。
訪問可選值時,代碼始終會同時處理 nil
和非 nil
的情況。當值缺失時,可以執行以下幾項操作:
- 當值為
nil
時,跳過對值進行操作的代碼; - 通過返回
nil
或使用可選鏈式調用中描述的?.
運算符來傳播nil
值; - 使用
??
運算符提供回退值; - 使用
!
運算符停止程序執行。
12.2 Optional Binding 可選邦定
可以使用可選綁定來判斷可選值是否包含值,如果包含則將該值用作臨時常量或變量。可選綁定可以與 if
、guard
和 while
語句一起使用,用于檢查可選值中的值,并將該值提取到常量或變量中,作為單個操作的一部分。
為 if
語句編寫一個可選綁定,下面的代碼意思為:如果 Int(possibleNumber)
返回的可選 Int
類型包含值,則將名為 actualNumber
的新常量設置為該可選類型包含的值。
let possibleNumber = "123"if let actualNumber = Int(possibleNumber){print("The string \"\(possibleNumber)\" has an integer value of \(actualNumber)")
}else{print("The string \"\(possibleNumber)\" couldn't be converted to an integer")
}
// The string "123" has an integer value of 123
如果在訪問其包含的值后不需要引用原始的可選常量或變量,則可以對 新的常量或變量使用相同的名稱,這段代碼首先檢查 myNumber
是否包含值,如果 myNumber
有值,則將名為 myNumber
的新常量的值設置為該值。
let possibleNumber = "123"
let myNumber = Int(possibleNumber)if let myNumber = myNumber{print("My number is \(myNumber)")
}
// My number is 123
可以將常量和變量與可選綁定一起使用。如果在 if
語句的第一個分支中操作 myNumber
的值,可以改寫為 if var myNumber
,這樣可選值中包含的值將作為變量而不是常量使用。在 if
語句主體中對 myNumber
所做的更改僅適用于該局部變量,而不會影響解包的原始可選常量或變量。
在單個 if
語句中包含 任意數量 的可選綁定和布爾條件并以逗號分隔。如果可選綁定中的任何值為 nil
或任何布爾條件的計算結果為 false,則整個 if
語句的條件將被視為 false:
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
12.3 Providing a Fallback Value 提供備選值
處理缺失值的另一種方法是使用空值合并運算符 ??
提供默認值。如果 ??
左側的可選項不為 nil
,則解包并使用該值;否則使用 ??
右側的值。下面的代碼會在指定姓名的情況下使用姓名問候某人,當姓名為 nil
時,使用通用問候語。
var name: String? = nil
let greeting = "Hello, " + (name ?? "friend") + "!"print(greeting) // Hello, friend!
13. Unwrapping 解包
13.1 Force Unwrapping 強制解包
當 nil
表示不可恢復的故障時,可以通過在可選項名稱末尾添加感嘆號 !
來訪問底層值,稱為強制解包可選項的值。強制解包非 nil
值時,返回其解包后的值;強制解包 nil
值會觸發運行時錯誤。
!
實際上是 fatalError(_:file:line:)
的縮寫,以下代碼展示了兩種等效的方法:
let possibleNumber = "123"
let convertedNumber = Int(possibleNumber)let number = convertedNumber!guard let number = convertedNumber else{fatalError("The number was invalid")
}
13.2 Implicitly Unwrapped Optionals 隱式解包可選值
可選值表示常量或變量可以“無值”。可以使用 if
語句檢查可選值是否存在,如果存在,可以使用可選綁定進行條件解包以訪問可選值。可選值在首次設置后始終會有一個值,在這種情況下無需在每次訪問可選值時都檢查并解包它,因為可以安全地假定始終有值。
這類可選值被定義為 隱式解包可選值,編寫隱式解包可選值時需要在可選值的類型后放置感嘆號 String!
而不是問號 String?
。使用可選值時,應在聲明可選值時在其類型后放置 !
,而不是在其名稱后放置 !
。
Swift 中隱式解包可選項的主要用途是在 類初始化期間。如果變量以后有可能變為 nil
,請勿使用隱式解包可選類型。如果需要在變量的生命周期內檢查其是否為 nil
值,應始終使用普通可選類型。
隱式解包可選類型在后臺與普通可選類型相同,但也可以像非可選值一樣使用,無需在每次訪問時都解包可選值。以下示例展示了在以顯式字符串形式訪問可選字符串和隱式解包可選字符串的包裝值時,兩者的行為差異:
let possibleString: String? = "An optional string"
let forcedString: String = possibleString! // 顯示解包let assumedString: String! = "An implicitly unwrapped optional string"
let implicitString: String = assumedString // 隱式解包
可以將隱式解包的可選值理解為 允許在需要時強制解包該可選值。當使用隱式解包的可選值時,Swift 首先會嘗試將其用作普通的可選值;如果不能被用作可選值,Swift 會強制解包該值。在上面的代碼中,可選值 supposedString
在賦值給 implicitString
之前被強制解包,因為 implicitString
具有顯式的非可選類型 String
。
在下面的代碼中,optionalString
沒有顯式類型,因此它是一個普通的可選值:
let assumedString: String! = "An implicitly unwrapped optional string"let optionalString = assumedString
如果隱式解包的可選類型為 nil
,當嘗試訪問其包裝值會觸發 runtime error。其結果與使用 !
強制解包一個不包含值的普通可選類型完全相同。可以像檢查普通可選類型一樣檢查隱式解包的可選類型是否為 nil
:
let assumedString: String! = "An implicitly unwrapped optional string"if assumedString != nil{print(assumedString!)
}
// An implicitly unwrapped optional string
還可以使用帶有可選綁定的隱式解包可選值,在單個語句中檢查并解包其值:
let assumedString: String! = "An implicitly unwrapped optional string"if let definiteString = assumedString{print(definiteString)
}
// An implicitly unwrapped optional string
14. Memory Safety 內存安全
除了上面類型安全和類型推斷中描述的防止類型不匹配的檢查之外,Swift 還保護代碼免受無效內存的影響。這種保護稱為內存安全,并包含以下要求:
- 值在讀取之前設置。防止與未初始化內存區域交互的保護也稱為明確初始化。
- 數組和緩沖區只能在有效索引處訪問。防止越界訪問的保護也稱為邊界安全。
- 內存只能在值的生命周期內訪問。防止釋放后使用錯誤的保護也稱為生命周期安全。
- 內存訪問僅以可證明安全的方式重疊。防止并發代碼中可能出現的數據競爭的保護也稱為線程安全。
如果你過去使用過類型不安全語言(如C++),可能熟悉上面列出的一些錯誤和 bug。Swift 中的安全代碼可以避免這些問題。
有時需要在安全范圍之外做些工作,例如由于語言或標準庫的限制,Swift 也提供了某些 API 的非安全版本。當使用名稱中包含“不安全”、“未檢查”或“非托管”等字眼的類型或方法時,需要自己承擔安全責任。
盡管 Swift 中的安全代碼,但仍然可能遇到錯誤和意外故障導致程序停止執行。Swift 提供了幾種指示和恢復錯誤的方法,但在某些情況下,處理錯誤的唯一安全方法是停止執行。如果需要保證服務永遠不會意外停止,請在其整體架構中加入容錯功能,以便它可以從任何組件的意外停止中恢復。
15. Error Handling 異常處理
可以使用錯誤處理來響應程序在執行過程中可能遇到的錯誤情況。
與可選值不同,錯誤處理用來定位失敗的根本原因,并在必要時將錯誤傳播到程序的其他部分。當函數遇到錯誤情況時會拋出一個錯誤。該函數的調用者可以捕獲該錯誤并做出適當的響應。
func canThrowAnError() throws{// 在函數中可能拋出錯誤
}
函數聲明中包含 throws
關鍵字表示可以拋出錯誤。調用一個可以拋出錯誤的函數時,需要在表達式前面添加 try
關鍵字。Swift 會自動將錯誤傳遞到當前作用域之外,直到被 catch
子句處理。
func canThrowAnError() throws{// 在函數中可能拋出錯誤
}do {try canThrowAnError()// 沒有異常拋出時運行到這個位置
}catch{// 拋出異常后跳轉到這里
}
do
語句會創建一個新的包含作用域,允許將錯誤傳播到一個或多個 catch
子句。以下示例展示了如何使用錯誤處理來響應不同的錯誤條件:
enum SandwichError: Error{case outOfCleanDishescase missingIngredients(ingredients:Int)
}func makeASandwich() throws{// ...
}func eatASandwich(){// ...
}func washDishes(){// ...
}func buyGroceries(ingredients: Int){// ...
}do {try makeASandwich()eatASandwich()
}catch SandwichError.outOfCleanDishes {washDishes()
}catch SandwichError.missingIngredients (let ingredients){buyGroceries(ingredients: ingredients)
}
16. Assertions and Preconditions 斷言與先決條件
Assertions
和 Preconditions
是在運行時進行的檢查,可以使用它們來確保在執行任何后續代碼之前滿足基本條件。如果斷言或先決條件中的布爾條件為真,則代碼將繼續照常執行;如果條件為假,則程序的當前狀態無效。
可以使用斷言和先決條件來表達在編碼時所做的假設和期望,斷言可以在開發過程中發現錯誤和不正確的假設;先決條件可以在生產環境中檢測問題。
除了在運行時驗證期望之外,斷言和先決條件也是代碼中一種有用的文檔形式。與上面“錯誤處理”中討論的錯誤條件不同,斷言和先決條件不用于可恢復或預期的錯誤。由于失敗的斷言或先決條件表示程序狀態無效,因此無法捕獲失敗的斷言。當斷言失敗時,程序中至少有一部分數據無效,但并無法知道無效的原因,也不知道其他狀態是否也無效。
使用斷言和前提條件并不能替代以不太可能出現無效條件的方式設計代碼,但使用它們來強制執行有效的數據和狀態,可以用在出現無效狀態時進行可預測地終止,并有助于更輕松地調試問題。如果不檢查假設,可能要等到很久以后當其他代碼部分開始明顯出現故障,并且用戶數據已悄無聲息地損壞時,才會注意到此類問題。一旦檢測到無效狀態,立即停止執行也有助于限制該無效狀態造成的損害。
斷言和先決條件的區別在于檢查的時間:斷言僅在調試版本中檢查,而先決條件在調試版本和生產版本中都會檢查。在生產版本中,斷言中的條件不會被評估,可以在開發過程中使用任意數量的斷言,而不會影響生產性能。
16.1 Debugging with Assertions 使用斷言Debug
可以通過調用 Swift 標準庫中的 assert(_:_:file:line:)
函數來編寫斷言,需要向此函數傳遞一個計算結果為 true 或 false 的表達式,以及一條在條件結果為 false 時顯示的消息。例如:
let age = -3
assert(age >= 0, "A person's age can't be less than zero.")
// 觸發斷言,因為條件不滿足
可以省略斷言消息:
let age = -3
assert(age >= 0)
如果代碼已經檢查了條件,則可以使用 assertionFailure()
函數來直接觸發斷言失敗:
let age = -3if 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.")
}
16.2 Enforcing Preconditions 執行先決條件
當條件有可能為假,但必須為真才能使代碼繼續執行時,可以使用 precondition,如使用前提條件檢查下標是否超出范圍,或檢查函數是否已傳遞有效值。
可以通過調用 precondition()
函數來編寫前提條件,需要向此函數傳遞一個計算結果為真或假的表達式,以及一條在條件結果為假時顯示的消息。例如:
let index = 0
precondition(index > 0, "Index muet be greater than zero")
可以調用 preconditionFailure()
函數來指示發生了失敗,例如 switch 的默認情況,但所有有效輸入數據都應該由 switch 的其他情況之一處理。