Swift 4.2語言參考之宣告
宣告 用於向你的程式引入新的名字或結構。例如,你可以使用宣告來引入函式和方法,引入變數和常量,還可以定義列舉,結構體,類,和協議型別。你還可以用宣告擴充套件命名型別的行為或在程式裡匯入其他地方定義的模組。
在 Swift 裡,大多數宣告在某種意義上也是定義,因為在定義的同時,也伴隨著實現或初始化。因為大多數協議成員只是宣告,而並不會實現他們。為了方便起見,也是因為這些區別在 Swift 並不重要, 宣告 術語包含了宣告和定義。
宣告語法
宣告 → 匯入-宣告
宣告 →
常量-宣告宣告 → 變數-宣告
宣告 → 類型別名-宣告
宣告 → 函式-宣告
宣告 → 列舉-宣告
宣告 → 結構體-宣告
宣告 → 類-宣告
宣告 → 協議-宣告
宣告 → 構造器-宣告
宣告 → 析構器-宣告
宣告 →
擴充套件-宣告宣告 → 下標-宣告
宣告 → 運算子-宣告
宣告 → 優先順序-組-宣告
頂級程式碼
Swift 的原始檔中的頂級程式碼(top-level code)由零個或多個語句、宣告和表示式組成。預設情況下,在一個原始檔的頂層宣告的變數,常量和其他具有命名的宣告可以被同模組中的每一個原始檔中的程式碼訪問。可以使用一個訪問級別修飾符來標記宣告來覆蓋這種預設行為,請參閱
頂級宣告語法
頂級宣告 → 語法 opt
程式碼塊
程式碼塊 code block 可以將一些宣告和控制結構組織在一起。它有如下的形式:
{
語句
}
程式碼塊中的 語句 包括宣告、表示式和各種其他型別的語句,它們按照在原始碼中的出現順序被依次執行。
程式碼塊語法
程式碼塊 →
{
語法 opt}
匯入宣告
匯入宣告 import declaration 讓你可以使用在其他檔案中宣告的內容。匯入語句的基本形式是匯入整個模組,它由 import
關鍵字和緊隨其後的模組名組成:
import 模組
可以對匯入操作提供更細緻的控制,如指定一個特殊的子模組或者指定一個模組或子模組中的某個宣告。提供了這些限制後,在當前作用域中,只有被匯入的符號是可用的,而不是整個模組中的所有宣告。
import 匯入型別 模組.符號名
import 模組.子模組
匯入宣告語法
匯入宣告 → 特性列表 opt
import
匯入型別 opt 匯入路徑匯入型別 →
typealias
|struct
|class
|enum
|protocol
|let
|var
|func
常量宣告
常量宣告 constant declaration 可以在程式中引入一個具有命名的常量。常量以關鍵字 let
來宣告,遵循如下格式:
let 常量 名稱: 型別 = 表示式
常量宣告在 constant name 和用於初始化的 expression 的值之間定義了一種不可變的繫結關係;當常量的值被設定之後,它就無法被更改。這意味著,如果常量以類物件來初始化,物件本身的內容是可以改變的,但是常量和該物件之間的繫結關係是不能改變的。
當一個常量被宣告為全域性常量時,它必須擁有一個初始值。在類或者結構中宣告一個常量時,它將作為常量屬性 constant property 。常量宣告不能是計算型屬性,因此也沒有存取方法。
如果常量名稱是元組形式,元組中每一項的名稱都會和初始化表示式中對應的值進行繫結。
let (firstNumber, secondNumber) = (10, 42)
在上例中, firstNumber
是一個值為 10
的常量, secondNumber
是一個值為 42
的常量。所有常量都可以獨立地使用:
print("The first number is \(firstNumber).")
// 列印 "The first number is 10."
print("The second number is \(secondNumber).")
// 列印 "The second number is 42."
當常量名稱的型別 (:
型別) 可以被推斷出時,型別標註在常量宣告中是可選的,正如 型別推斷中所描述的。
宣告一個常量型別屬性要使用 static
宣告修飾符。型別屬性在 型別屬性中有介紹。
如果還想獲得更多關於常量的資訊或者想在使用中獲得幫助,請參考 常量和變數 和 儲存屬性。
常量宣告語法
常量宣告 → 特性列表 opt 宣告修飾符 opt
let
模式構造器列表模式構造器列表 → 模式構造器 | 模式構造器
,
模式構造器列表構造器 →
=
表示式
變數宣告
變數宣告 variable declaration 可以在程式中引入一個具有命名的變數,它以關鍵字 var
來宣告。
變數宣告有幾種不同的形式,可以宣告不同種類的命名值和可變值,如儲存型和計算型變數和屬性,屬性觀察器,以及靜態變數屬性。所使用的宣告形式取決於變數宣告的適用範圍和打算宣告的變數型別。
注意
也可以在協議宣告中宣告屬性,詳情請參閱 協議屬性宣告。
可以在子類中重寫繼承來的變數屬性,使用 override
宣告修飾符標記屬性的宣告即可,詳情請參閱 重寫。
儲存型變數和儲存型變數屬性
使用如下形式宣告一個儲存型變數或儲存型變數屬性:
var 變數名稱: 型別 = 表示式
可以在全域性範圍,函式內部,或者在類和結構的宣告中使用這種形式來宣告一個變數。當變數以這種形式在全域性範圍或者函式內部被宣告時,它代表一個儲存型變數。當它在類或者結構中被宣告時,它代表一個儲存型變數屬性 stored variable property 。
用於初始化的表示式不可以在協議的宣告中出現,在其他情況下,該表示式是可選的。如果沒有初始化表示式,那麼變數宣告必須包含型別標註 (:
型別)。
如同常量宣告,如果變數名稱是元組形式,元組中每一項的名稱都會和初始化表示式中對應的值進行繫結。
正如名字所示,儲存型變數和儲存型變數屬性的值會儲存在記憶體中。
計算型變數和計算型屬性
使用如下形式宣告一個計算型變數或計算型屬性:
var 變數名稱: 型別 {
get {
語句
}
set(setter 名稱) {
語句
}
}
可以在全域性範圍、函式內部,以及類、結構、列舉、擴充套件的宣告中使用這種形式的宣告。當變數以這種形式在全域性範圍或者函式內部被宣告時,它表示一個計算型變數。當它在類、結構、列舉、擴充套件宣告的上下文中被宣告時,它表示一個計算型屬性 computed property 。
getter 用來讀取變數值,setter 用來寫入變數值。setter 子句是可選的,getter 子句是必須的。不過也可以將這些子句都省略,直接返回請求的值,正如在 只讀計算型屬性 中描述的那樣。但是如果提供了一個 setter 子句,就必須也提供一個 getter 子句。
setter 的圓括號以及 setter 名稱是可選的。如果提供了 setter 名稱,它就會作為 setter 的引數名稱使用。如果不提供 setter 名稱,setter 的引數的預設名稱為 newValue
,正如在 [便捷 setter 宣告] (https://docs.swift.org/swift-book/LanguageGuide/Properties.html#ID260)中描述的那樣。
與儲存型變數和儲存型屬性不同,計算型變數和計算型屬性的值不儲存在記憶體中。
要獲得更多關於計算型屬性的資訊和例子,請參閱 計算型屬性。
儲存型變數和屬性的觀察器
可以在宣告儲存型變數或屬性時提供 willSet
和 didSet
觀察器。一個包含觀察器的儲存型變數或屬性以如下形式宣告:
var 變數名稱: 型別 = 表示式 {
willSet(setter 名稱) {
語句
}
didSet(setter 名稱) {
語句
}
}
可以在全域性範圍、函式內部,或者類、結構的宣告中使用這種形式的宣告。當變數以這種形式在全域性範圍或者函式內部被宣告時,觀察器表示一個儲存型變數觀察器。當它在類和結構的宣告中被宣告時,觀察器表示一個屬性觀察器。
可以為任何儲存型屬性新增觀察器。也可以通過重寫父類屬性的方式為任何繼承的屬性(無論是儲存型還是計算型的)新增觀察器 ,正如重寫屬性觀察器中所描述的。
用於初始化的表示式在類或者結構的宣告中是可選的,但是在其他宣告中則是必須的。如果可以從初始化表示式中推斷出型別資訊,那麼可以不提供型別標註。
當變數或屬性的值被改變時,willSet 和 didSet 觀察器提供了一種觀察方法。觀察器會在變數的值被改變時呼叫,但不會在初始化時被呼叫。
willSet
觀察器只在變數或屬性的值被改變之前呼叫。新的值作為一個常量傳入 willSet 觀察器,因此不可以在 willSet
中改變它。 didSet
觀察器在變數或屬性的值被改變後立即呼叫。和 willSet
觀察器相反,為了方便獲取舊值,舊值會傳入 didSet
觀察器。這意味著,如果在變數或屬性的 didSet
觀察器中設定值,設定的新值會取代剛剛在 willSet
觀察器中傳入的那個值。
在 willSet
和 didSet
中,圓括號以及其中的 setter 名稱是可選的。如果提供了一個 setter 名稱,它就會作為 willSet
和 didSet
的引數被使用。如果不提供 setter 名稱, willSet
觀察器的預設引數名為 newValue
, didSet
觀察器的預設引數名為 oldValue
。
提供了 willSet
時, didSet
是可選的。同樣的,提供了 didSet
時, willSet
則是可選的。
要獲得更多資訊以及檢視如何使用屬性觀察器的例子,請參閱屬性觀察器。
型別變數屬性
要宣告一個型別變數屬性,用 static
宣告修飾符標記該宣告。類可以改用 class
宣告修飾符標記類的型別計算型屬性從而允許子類重寫超類的實現。型別屬性在型別屬性章節有詳細討論。
注意
在一個類宣告中,使用關鍵字
static
與同時使用class
和final
去標記一個宣告的效果相同。變數宣告語法
變數宣告 → 變數宣告頭 變數名稱 型別標註 getter-setter 程式碼塊
變數宣告 → 變數宣告頭 變數名稱 型別標註 getter-setter-keyword 程式碼塊
變數宣告 → 變數宣告頭 變數名稱 構造器 willSet-didSet 程式碼塊
變數宣告 → 變數宣告頭 變數名稱 型別標註 構造器 opt willSet-didSet 程式碼塊
變數宣告頭 → 特性列表 opt 宣告修飾符 opt
var
變數名稱 → 識別符號
getter-setter 程式碼塊 → 程式碼塊
getter-setter 程式碼塊 →
{
getter 子句 setter 子句 opt}
getter-setter 程式碼塊 →
{
setter 子句 getter 子句}
getter 子句 → 特性列表 opt 轉義修飾符 opt
get
程式碼塊setter 子句 → 特性列表 opt 轉義修飾符 opt
set
setter 名稱 opt 程式碼塊setter 名稱 →
(
識別符號)
getter-setter-keyword 程式碼塊 →
{
getter-keyword 子句 setter-keyword 子句 opt}
getter-setter-keyword 程式碼塊 →
{
setter-keyword 子句 getter-keyword 子句}
getter-keyword 子句 → 特性列表 opt 轉義修飾符 opt
get
setter-keyword 子句 → 特性列表 opt 轉義修飾符 opt
set
willSet-didSet 程式碼塊 →
{
willSet 子句 didSet 子句 opt}
willSet-didSet 程式碼塊 →
{
didSet 子句 willSet 子句 opt}
類型別名宣告
類型別名 type alias declaration 宣告可以在程式中為一個既有型別宣告一個別名。類型別名宣告語句使用關鍵字 typealias
宣告,遵循如下的形式:
typealias 名稱 = 現有型別
當宣告一個型別的別名後,可以在程式的任何地方使用別名來代替現有型別。現有型別可以是具有命名的型別或者混合型別。類型別名不產生新的型別,它只是使用別名來引用現有型別。 類型別名宣告可以通過泛型引數來給一個現有泛型型別提供名稱。類型別名為現有型別的一部分或者全部泛型引數提供具體型別。例如:
typealias StringDictionary<Value> = Dictionary<String, Value>
// 下列兩個字典擁有同樣的型別
var dictionary1: StringDictionary<Int> = [:]
var dictionary2: Dictionary<String, Int> = [:]
當一個類型別名帶著泛型引數一起被宣告時,這些引數的約束必須與現有引數的約束完全匹配。例如:
typealias DictionaryOfInts<Key: Hashable> = Dictionary<Key, Int>
因為類型別名可以和現有型別相互交換使用,類型別名不可以引入額外的型別約束。 在協議宣告中,類型別名可以為那些經常使用的型別提供一個更短更方便的名稱,例如:
因為類型別名可以和現有型別相互交換使用,類型別名不可以引入額外的型別約束。 在協議宣告中,類型別名可以為那些經常使用的型別提供一個更短更方便的名稱,例如:
protocol Sequence {
associatedtype Iterator: IteratorProtocol
typealias Element = Iterator.Element
}
func sum<T: Sequence>(_ sequence: T) -> Int where T.Element == Int {
// ...
}
假如沒有類型別名, sum
函式將必須引用關聯型別通過 T.Iterator.Element
的形式來替代 T.Element
。
另請參閱 協議關聯型別宣告.
類型別名宣告語法
類型別名宣告 → 特性列表 opt 訪問級別修飾符 opt
typealias
類型別名名稱 泛型形參子句 opt 類型別名賦值類型別名名稱 → 識別符號
類型別名賦值 →
=
型別
函式宣告
使用函式宣告 function declaration 在程式中引入新的函式或者方法。在類、結構體、列舉,或者協議中宣告的函式會作為方法。函式宣告使用關鍵字 func
,遵循如下的形式:
func 函式名稱(引數列表) -> 返回型別 {
語句
}
如果函式返回 Void
型別,返回型別可以省略,如下所示:
func 函式名稱(引數列表) {
語句
}
每個引數的型別都要標明,因為它們不能被推斷出來。如果您在某個引數型別前面加上了 inout
,那麼這個引數就可以在這個函式作用域當中被修改。更多關於 inout
引數的討論,請參閱輸入輸出引數。
函式可以使用元組型別作為返回型別來返回多個值。
函式定義可以出現在另一個函式宣告內。這種函式被稱作巢狀函式nested function。如果巢狀函式捕獲的值保證永遠不會轉義(如in-out引數)或作為非掃描函式引數傳遞,則該函式是非掃描的。否則,巢狀函式是一個轉義函式。更多關於巢狀函式的討論,請參閱巢狀函式。
引數名
函式的引數列表由一個或多個函式引數組成,引數間以逗號分隔。函式呼叫時的引數順序必須和函式宣告時的引數順序一致。最簡單的引數列表有著如下的形式:
引數名稱: 引數型別
一個引數有一個內部名稱,這個內部名稱可以在函式體內被使用。還有一個外部名稱,當呼叫函式時這個外部名稱被作為實參的標籤來使用。預設情況下,第一個引數的外部名稱會被省略,第二個和之後的引數使用它們的內部名稱作為它們的外部名稱。例如:
func f(x: Int, y: Int) -> Int { return x + y }
f(x: 1, y: 2) // 引數 y 有標籤,引數 x 則沒有
可以按照如下兩種形式之一,重寫引數名稱的預設行為:
argument label parameter name: parameter type
_ parameter name: parameter type
在內部引數名稱前的名稱會作為這個引數的外部名稱,這個名稱可以和內部引數的名稱不同。外部引數名稱在函式被呼叫時必須被使用,即對應的引數在方法或函式被呼叫時必須有外部名。
內部引數名稱前的下劃線 (_
) 可使該引數在函式被呼叫時沒有名稱。在函式或方法呼叫時,對應的引數必須沒有名字。
func repeatGreeting(_ greeting: String, count n: Int) { /* Greet n times */ }
repeatGreeting("Hello, world!", count: 2) // count 被標記, greeting 沒有標記
輸入輸出引數
輸入輸出引數被傳遞時遵循如下規則:
1.函式呼叫時,引數的值被拷貝。
2.函式體內部,拷貝後的值被修改。
3.函式返回後,拷貝後的值被賦值給原引數。
這種行為被稱為拷入拷出 copy-in copy-out 或值結果呼叫 call by value result。例如,當一個計算型屬性或者一個具有屬性觀察器的屬性被用作函式的輸入輸出引數時,其 getter 會在函式呼叫時被呼叫,而其 setter 會在函式返回時被呼叫。
作為一種優化手段,當引數值儲存在記憶體中的實體地址時,在函式體內部和外部均會使用同一記憶體位置。這種優化行為被稱為引用呼叫 call by reference,它滿足了拷入拷出模式的所有要求,且消除了複製帶來的開銷。在程式碼中,要規範使用拷入拷出模式,不要依賴於引用呼叫。
在函式中,不要訪問作為輸入輸出引數傳遞的值,即使原始值在當前範圍內可用。訪問原始是一個同時訪問的值,這違反了斯威夫特的記憶體排他性保證。出於同樣的原因,不能將相同的值傳遞給多個 in-out 引數。
有關記憶體安全和記憶體排他性的更多資訊,請參見 記憶體安全。
捕獲 in-out 引數的閉包或巢狀函式必須是非逃逸閉包。如果您需要捕獲一個輸入輸出引數,而不需要對其進行更改,或者需要觀察其他程式碼所做的更改,則使用捕獲列表來顯式地不可變地捕獲該引數。
func someFunction(a: inout Int) -> () -> Int {
return { [a] in return a + 1 }
}
如果需要捕獲和變異輸入輸出引數,則使用顯式的本地副本,例如在多執行緒程式碼中,以確保在函式返回之前完成所有變體。
func multithreadedFunction(queue: DispatchQueue, x: inout Int) {
// Make a local copy and manually copy it back.
var localX = x
defer { x = localX }
// Operate on localX asynchronously, then wait before returning.
queue.async { someMutatingOperation(&localX) }
queue.sync {}
}
有關 in-out 引數的更多討論和示例,請參見 輸入輸出引數。
特殊型別引數
引數可以被忽略,數量可以不固定,還可以為其提供預設值,使用形式如下:
_ : 引數型別
引數名稱: 引數型別...
引數名稱: 引數型別 = 預設引數值
以下劃線 (_
) 命名的引數會被顯式忽略,無法在函式體內使用。
一個引數的基本型別名稱如果緊跟著三個點 (...
) ,會被視為可變引數。一個函式至多可以擁有一個可變引數,且必須是最後一個引數。可變引數會作為包含該引數型別元素的陣列處理。舉例來講,可變引數 Int...
會作為 [Int]
來處理。關於使用可變引數的例子,請參可變引數。
如果在引數型別後面有一個以等號 (=
) 連線的表示式,該引數會擁有預設值,即給定表示式的值。當函式被呼叫時,給定的表示式會被求值。如果引數在函式呼叫時被省略了,就會使用其預設值。
func f(x: Int = 42) -> Int { return x }
f() // Valid, uses default value
f(x: 7) // Valid, uses the value provided
f(7) // Invalid, missing argument label
特殊型別方法
列舉或結構體的方法如果會修改 self
,則必須以 mutating
宣告修飾符標記
子類重寫超類中的方法必須以 override
宣告修飾符標記。重寫方法時不使用 override
修飾符,或者被 override
修飾符修飾的方法並未對超類方法構成重寫,都會導致編譯錯誤。
列舉或者結構體中的型別方法,要以 static
宣告修飾符標記,而對於類中的型別方法,除了使用 static
,還可使用 class
宣告修飾符標記。
丟擲錯誤的函式和方法
可以丟擲錯誤的函式或方法必須使用 throws
關鍵字標記。這類函式和方法被稱為丟擲函式和丟擲方法。它們有著下面的形式:
func 函式名稱(引數列表) throws -> 返回型別 {
語句
}
丟擲函式或丟擲方法的呼叫必須包裹在 try
或者 try!
表示式中(也就是說,在作用域內使用 try!
或者 try!
運算子)。
throws
關鍵字是函式的型別的一部分,非丟擲函式是丟擲函式的子型別。所以,可以在使用丟擲函式的地方使用非丟擲函式。
不能僅基於函式能否丟擲錯誤來進行函式過載。也就是說,可以基於函式的函式型別的引數能否丟擲錯誤來進行函式過載。
丟擲方法不能重寫非丟擲方法,而且丟擲方法不能滿足協議對於非丟擲方法的要求。也就是說,非丟擲方法可以重寫丟擲方法,而且非丟擲方法可以滿足協議對於丟擲方法的要求。
重拋錯誤的函式和方法
函式或方法可以使用 rethrows
關鍵字來宣告,從而表明僅當該函式或方法的一個函式型別的引數丟擲錯誤時,該函式或方法才丟擲錯誤。這類函式和方法被稱為重拋函式和重拋方法。重新丟擲錯誤的函式或方法必須至少有一個引數的型別為丟擲函式。
func someFunction(callback: () throws -> Void) rethrows {
try callback()
}
一個重拋函式或方法只能在 catch
子句內包含一個 throw
語句。這允許在 do
-catch
程式碼塊內呼叫丟擲函式,並通過丟擲不同的錯誤處理 catch
子句中的錯誤。此外, catch
子句必須只處理由重發函式的投擲引數之一引發的錯誤。例如,以下是無效的,因為 catch
子句將處理由 alwaysThrows()
所引發的錯誤。
func alwaysThrows() throws {
throw SomeError.error
}
func someFunction(callback: () throws -> Void) rethrows {
do {
try callback()
try alwaysThrows() // Invalid, alwaysThrows() isn't a throwing parameter
} catch {
throw AnotherError.error
}
}
丟擲方法不能覆蓋重丟擲方法,丟擲方法不能滿足重丟擲方法的協議要求。也就是說,重新丟擲方法可以覆蓋丟擲方法,並且重新丟擲方法可以滿足丟擲方法的協議要求。
永不返回的函式
Swift 定義了 Never
型別,它表示函式或者方法不會返回給它的呼叫者。 Never
返回型別的函式或方法可以稱為不歸,不歸函式、方法要麼引發不可恢復的錯誤,要麼永遠不停地運作,這會使呼叫後本應執行得程式碼就不再執行了。但即使是不歸函式、方法,拋錯函式和重丟擲函式也可以將程式控制轉移到合適的 catch
程式碼塊。
可以呼叫一個不返回函式或方法來結束一個守護語句的 else
子句,如
Guard 語句.
可以重寫不返回的方法,但新方法必須保留其返回型別和不返回行為。
函式宣告語法
函式宣告 → 函式頭 函式名 泛型形參子句 opt函式簽名 泛型 where 子句 opt 函式體 opt
function-signature → parameter-clause
throws
opt function-result opt函式體 → 程式碼塊
引數子句 →
(
)
|(
引數列表)
引數 → 外部引數名 opt 內部引數名 型別標註預設引數子句 opt
外部引數名 → 識別符號
內部引數名 → 識別符號
預設引數子句 →
=
表示式
列舉宣告
在程式中使用列舉宣告 enumeration declaration 來引入一個列舉型別。
列舉宣告有兩種基本形式,使用關鍵字 enum
來宣告。列舉宣告體包含零個或多個值,稱為列舉用例,還可包含任意數量的宣告,包括計算型屬性、例項方法、型別方法、構造器、類型別名,甚至其他列舉、結構體和類。列舉宣告不能包含析構器或者協議宣告。
列舉型別可以採納任意數量的協議,但是列舉不能從類、結構體和其他列舉繼承。
不同於類或者結構體,列舉型別並不隱式提供預設構造器,所有構造器必須顯式宣告。一個構造器可以委託給列舉中的其他構造器,但是構造過程僅當構造器將一個列舉用例賦值給 self
後才算完成。
和結構體類似但是和類不同,列舉是值型別。列舉例項在被賦值到變數或常量時,或者傳遞給函式作為引數時會被複制。更多關於值型別的資訊,請參閱 結構體和列舉是值型別。
可以擴充套件列舉型別,正如在 擴充套件宣告中討論的一樣。
任意型別的列舉用例
如下的形式聲明瞭一個包含任意型別列舉用例的列舉變數:
enum 列舉名稱: 採納的協議 {
case 列舉用例1
case 列舉用例2(關聯值型別)
}
這種形式的列舉宣告在其他語言中有時被叫做可識別聯合。
在這種形式中,每個用例塊由關鍵字 case
開始,後面緊接一個或多個以逗號分隔的列舉用例。每個用例名必須是獨一無二的。每個用例也可以指定它所儲存的指定型別的值,這些型別在關聯值型別的元組中被指定,緊跟用例名之後。
具有關聯值的列舉用例可以像函式一樣使用,通過指定的關聯值建立列舉例項。和真正的函式一樣,你可以獲取列舉用例的引用,然後在後續程式碼中呼叫它。
enum Number {
case integer(Int)
case real(Double)
}
let f = Number.integer
// f 的型別為 (Int) -> Number
// 利用 f 把一個整數陣列轉成 Number 陣列
let evenInts: [Number] = [0, 2, 4, 6].map(f)
要獲得更多關於具有關聯值的列舉用例的資訊和例子,請參閱 關聯值。
遞迴列舉
列舉型別可以具有遞迴結構,就是說,列舉用例的關聯值型別可以是列舉型別自身。然而,列舉型別的例項具有值語義,這意味著它們在記憶體中有固定佈局。為了支援遞迴,編譯器必須插入一個間接層。
要讓某個列舉用例支援遞迴,使用 indirect
宣告修飾符標記該用例。
enum Tree<T> {
case empty
indirect case node(value: T, left: Tree, right: Tree)
}
要讓一個列舉型別的所有用例都支援遞迴,使用 indirect
修飾符標記整個列舉型別,當列舉有多個用例且每個用例都需要使用 indirect
修飾符標記的時候這將非常便利。
被 indirect
修飾符標記的列舉用例必須有一個關聯值。使用 indirect 修飾符標記的列舉型別可以既包含有關聯值的用例,同時還可包含沒有關聯值的用例。但是,它不能再單獨使用 indirect
修飾符來標記某個用例。
擁有原始值的列舉用例
以下形式聲明瞭一種列舉型別,其中各個列舉用例的型別均為同一種基本型別:
enum 列舉名稱: 原始值型別, 採納的協議 {
case 列舉用例1 = 原始值1
case 列舉用例2 = 原始值2
}
在這種形式中,每一個用例塊由 case
關鍵字開始,後面緊跟一個或多個以逗號分隔的列舉用例。和第一種形式的列舉用例不同,這種形式的列舉用例包含一個基礎值,叫做原始值,各個列舉用例的原始值的型別必須相同。這些原始值的型別通過原始值型別指定,必須表示一個整數、浮點數、字串或者字元。原始值型別必須符合 Equatable
協議和下列字面量轉換協議中的一種:整型字面量需符合 ExpressibleByIntegerLiteral
協議,浮點型字面量需符合 ExpressibleByFloatLiteral
協議,包含任意數量字元的字串型字面量需符合 ExpressibleByUnicodeScalarLiteral
和 ExpressibleByExtendedGraphemeClusterLiteral
協議,僅包含一個單一字元的字串型字面量。每一個用例的名字和原始值必須唯一。
如果原始值型別被指定為 Int
,則不必為用例顯式地指定原始值,它們會隱式地被賦值 0
, 1
, 2
等。每個未被賦值的 Int
型別的用例會被隱式地賦值,其值為上一個用例的原始值加 1
。
enum ExampleEnum: Int {
case a, b, c = 5, d
}
在上面的例子中, ExampleEnum.a
的原始值是 0
, ExampleEnum.b
的原始值是 1
。因為 ExampleEnum.c
的原始值被顯式地設定為 5
,因此 ExampleEnum.d
的原始值會自動增長為 6
。
如果原始值型別被指定為 String
型別,你不用明確地為用例指定原始值,每個沒有指定原始值的用例會隱式地將用例名字作為原始值。
enum GamePlayMode: String {
case cooperative, individual, competitive
}
在上面的例子中, GamePlayMode.cooperative
的原始值是 "cooperative"
,而 GamePlayMode.individual
的原始值是 "individual"
。 GamePlayMode.competitive
的原始值是"competitive"
。
列舉用例具有原始值的列舉型別隱式地符合定義在 Swift 標準庫中的 RawRepresentable
協議。所以,它們擁有一個 rawValue
屬性和一個可失敗構造器 init?(rawValue: RawValue)
。可以使用 rawValue
屬性去獲取列舉用例的原始值,例如 ExampleEnum.b.rawValue
。還可以根據原始值來建立一個相對應的列舉用例,只需呼叫列舉的可失敗構造器,例如 ExampleEnum(rawValue: 5)
,這個可失敗構造器返回一個可選型別的用例。要獲得更多關於具有原始值的列舉用例的資訊和例子,請參閱 原始值。
訪問列舉用例
使用點語法 (.
) 來引用列舉型別的列舉用例,例如 EnumerationType.enumerationCase
。當列舉型別可以由上下文推斷而出時,可以省略它(但是 . 仍然需要),正如 列舉語法和 顯式成員表示式所述。
可以使用 switch
語句來檢驗列舉用例的值,正如 使用 switch
語句匹配列舉值 Matching Enumeration Values with a Switch Statement所述。 列舉型別是模式匹配的,依靠 switch 語句 case 塊中的列舉用例模式,正如 列舉用例模式所述。
列舉宣告語法