這里的教程為Swift官方教程中文版。

類型(Types)


1.0 翻譯:lyuka 校對:numbbbbb, stanzhai

2.0 翻譯+校對:EudeMorgen

2.1 翻譯:mmoaay

本頁包含內(nèi)容:

Swift 語言存在兩種類型:命名型類型和復(fù)合型類型。命名型類型是指定義時可以給定名字的類型。命名型類型包括類、結(jié)構(gòu)體、枚舉和協(xié)議。比如,一個用戶定義的類 MyClass 的實(shí)例擁有類型 MyClass。除了用戶定義的命名型類型,Swift 標(biāo)準(zhǔn)庫也定義了很多常用的命名型類型,包括那些表示數(shù)組、字典和可選值的類型。

那些通常被其它語言認(rèn)為是基本或原始的數(shù)據(jù)型類型,比如表示數(shù)字、字符和字符串的類型,實(shí)際上就是命名型類型,這些類型在 Swift 標(biāo)準(zhǔn)庫中是使用結(jié)構(gòu)體來定義和實(shí)現(xiàn)的。因?yàn)樗鼈兪敲皖愋?,因此你可以按?擴(kuò)展擴(kuò)展聲明 中討論的那樣,聲明一個擴(kuò)展來增加它們的行為以滿足你程序的需求。

復(fù)合型類型是沒有名字的類型,它由 Swift 本身定義。Swift 存在兩種復(fù)合型類型:函數(shù)類型和元組類型。一個復(fù)合型類型可以包含命名型類型和其它復(fù)合型類型。例如,元組類型 (Int, (Int, Int)) 包含兩個元素:第一個是命名型類型 Int,第二個是另一個復(fù)合型類型 (Int, Int)。

本節(jié)討論 Swift 語言本身定義的類型,并描述 Swift 中的類型推斷行為。

類型語法
類型數(shù)組類型 | 字典類型 | 函數(shù)類型 | 類型標(biāo)識 | 元組類型 | 可選類型 | 隱式解析可選類型 | 協(xié)議合成類型 | 元型類型 | 任意類型 | 自身類型

類型注解

類型注解顯式地指定一個變量或表達(dá)式的值。類型注解始于冒號 : 終于類型,比如下面兩個例子:

let someTuple: (Double, Double) = (3.14159, 2.71828)
func someFunction(a: Int) { /* ... */ }

在第一個例子中,表達(dá)式 someTuple 的類型被指定為 (Double, Double)。在第二個例子中,函數(shù) someFunction 的參數(shù) a 的類型被指定為 Int。

類型注解可以在類型之前包含一個類型特性的可選列表。

類型注解語法
類型注解: 特性列表可選 輸入輸出參數(shù)可選 類型

類型標(biāo)識符

類型標(biāo)識符引用命名型類型,還可引用命名型或復(fù)合型類型的別名。

大多數(shù)情況下,類型標(biāo)識符引用的是與之同名的命名型類型。例如類型標(biāo)識符 Int 引用命名型類型 Int,同樣,類型標(biāo)識符 Dictionary<String, Int> 引用命名型類型 Dictionary<String, Int>。

在兩種情況下類型標(biāo)識符不引用同名的類型。情況一,類型標(biāo)識符引用的是命名型或復(fù)合型類型的類型別名。比如,在下面的例子中,類型標(biāo)識符使用 Point 來引用元組 (Int, Int)

typealias Point = (Int, Int)
let origin: Point = (0, 0)

情況二,類型標(biāo)識符使用點(diǎn)語法(.)來表示在其它模塊或其它類型嵌套內(nèi)聲明的命名型類型。例如,下面例子中的類型標(biāo)識符引用在 ExampleModule 模塊中聲明的命名型類型 MyType

var someValue: ExampleModule.MyType

類型標(biāo)識符語法
</a> 類型標(biāo)識符類型名稱 泛型參數(shù)子句可選 | 類型名稱 泛型參數(shù)子句可選 . 類型標(biāo)識符
類型名稱標(biāo)識符

元組類型

元組類型是使用括號括起來的零個或多個類型,類型間用逗號隔開。

你可以使用元組類型作為一個函數(shù)的返回類型,這樣就可以使函數(shù)返回多個值。你也可以命名元組類型中的元素,然后用這些名字來引用每個元素的值。元素的名字由一個標(biāo)識符緊跟一個冒號 (:) 組成。函數(shù)和多返回值 章節(jié)里有一個展示上述特性的例子。

當(dāng)一個元組類型的元素有名字的時候,這個名字就是類型的一部分。

var someTuple = (top: 10, bottom: 12)  // someTuple 的類型為 (top: Int, bottom: Int)
someTuple = (top: 4, bottom: 42) // 正確:命名類型匹配
someTuple = (9, 99)              // 正確:命名類型被自動推斷
someTuple = (left: 5, right: 5)  // 錯誤:命名類型不匹配

Void 是空元組類型 () 的別名。如果括號內(nèi)只有一個元素,那么該類型就是括號內(nèi)元素的類型。比如,(Int) 的類型是 Int 而不是 (Int)。所以,只有當(dāng)元組類型包含的元素個數(shù)在兩個及以上時才可以命名元組元素。

元組類型語法 </a> 元組類型( 元組類型元素列表 可選 )
元組類型元素列表元組類型元素 | 元組類型元素 , 元組類型元素列表
</a> 元組類型元素元素名 類型注解 | 類型 元素名標(biāo)識符

函數(shù)類型

函數(shù)類型表示一個函數(shù)、方法或閉包的類型,它由參數(shù)類型和返回值類型組成,中間用箭頭(->)隔開:

參數(shù)類型 -> 返回值類型

參數(shù)類型是由逗號間隔的類型列表。由于參數(shù)類型和返回值類型可以是元組類型,所以函數(shù)類型支持多參數(shù)與多返回值的函數(shù)與方法。

你可以對函數(shù)參數(shù)使用 autoclosure 特性。這會自動將參數(shù)表達(dá)式轉(zhuǎn)化為閉包,表達(dá)式的結(jié)果即閉包返回值。這從語法結(jié)構(gòu)上提供了一種便捷:延遲對表達(dá)式的求值,直到其值在函數(shù)體中被使用。以自動閉包做為參數(shù)的函數(shù)類型的例子詳見 自動閉包 。

函數(shù)類型可以擁有一個可變長參數(shù)作為參數(shù)類型中的最后一個參數(shù)。從語法角度上講,可變長參數(shù)由一個基礎(chǔ)類型名字緊隨三個點(diǎn)(...)組成,如 Int...??勺冮L參數(shù)被認(rèn)為是一個包含了基礎(chǔ)類型元素的數(shù)組。即 Int... 就是 [Int]。關(guān)于使用可變長參數(shù)的例子,請參閱 可變參數(shù)。

為了指定一個 in-out 參數(shù),可以在參數(shù)類型前加 inout 前綴。但是你不可以對可變長參數(shù)或返回值類型使用 inout。關(guān)于這種參數(shù)的詳細(xì)講解請參閱 輸入輸出參數(shù)

函數(shù)和方法中的參數(shù)名并不是函數(shù)類型的一部分。例如:

func someFunction(left: Int, right: Int) {}
func anotherFunction(left: Int, right: Int) {}
func functionWithDifferentLabels(top: Int, bottom: Int) {}

var f = someFunction // 函數(shù)f的類型為 (Int, Int) -> Void, 而不是 (left: Int, right: Int) -> Void.
f = anotherFunction              // 正確
f = functionWithDifferentLabels  // 正確

func functionWithDifferentArgumentTypes(left: Int, right: String) {}
func functionWithDifferentNumberOfArguments(left: Int, right: Int, top: Int) {}

f = functionWithDifferentArgumentTypes     // 錯誤
f = functionWithDifferentNumberOfArguments // 錯誤

如果一個函數(shù)類型包涵多個箭頭(->),那么函數(shù)類型將從右向左進(jìn)行組合。例如,函數(shù)類型 Int -> Int -> Int 可以理解為 Int -> (Int -> Int),也就是說,該函數(shù)類型的參數(shù)為 Int 類型,其返回類型是一個參數(shù)類型為 Int,返回類型為 Int 的函數(shù)類型。

函數(shù)類型若要拋出錯誤就必須使用 throws 關(guān)鍵字來標(biāo)記,若要重拋錯誤則必須使用 rethrows 關(guān)鍵字來標(biāo)記。throws 關(guān)鍵字是函數(shù)類型的一部分,非拋出函數(shù)是拋出函數(shù)函數(shù)的一個子類型。因此,在使用拋出函數(shù)的地方也可以使用不拋出函數(shù)。拋出和重拋函數(shù)的相關(guān)描述見章節(jié) 拋出函數(shù)與方法重拋函數(shù)與方法。

函數(shù)類型語法
</a> 函數(shù)類型特性列表可選 函數(shù)類型子句 throws可選 -> 類型 函數(shù)類型特性列表可選 函數(shù)類型子句 rethrows- -> 類型 函數(shù)類型子句 → (-)- 函數(shù)類型子句 → (函數(shù)類型參數(shù)列表...-可選)- </a> 函數(shù)類型參數(shù)列表函數(shù)類型參數(shù) | 函數(shù)類型參數(shù)函數(shù)類型參數(shù)列表 函數(shù)類型參數(shù)特性列表可選 輸入輸出參數(shù)可選 類型 | 參數(shù)標(biāo)簽 類型注解 參數(shù)標(biāo)簽標(biāo)識符

數(shù)組類型

Swift 語言為標(biāo)準(zhǔn)庫中定義的 Array<Element> 類型提供了如下語法糖:

[類型]

換句話說,下面兩個聲明是等價的:

let someArray: Array<String> = ["Alex", "Brian", "Dave"]
let someArray: [String] = ["Alex", "Brian", "Dave"]

上面兩種情況下,常量 someArray 都被聲明為字符串?dāng)?shù)組。數(shù)組的元素也可以通過下標(biāo)訪問:someArray[0] 是指第 0 個元素 "Alex"。

你也可以嵌套多對方括號來創(chuàng)建多維數(shù)組,最里面的方括號中指明數(shù)組元素的基本類型。比如,下面例子中使用三對方括號創(chuàng)建三維整數(shù)數(shù)組:

var array3D: [[[Int]]] = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]

訪問一個多維數(shù)組的元素時,最左邊的下標(biāo)指向最外層數(shù)組的相應(yīng)位置元素。接下來往右的下標(biāo)指向第一層嵌入的相應(yīng)位置元素,依次類推。這就意味著,在上面的例子中,array3D[0][[1, 2], [3, 4]]array3D[0][1][3, 4],array3D[0][1][1] 則是 4

關(guān)于 Swift 標(biāo)準(zhǔn)庫中 Array 類型的詳細(xì)討論,請參閱 數(shù)組

數(shù)組類型語法
數(shù)組類型[ 類型 ]

字典類型

Swift 語言為標(biāo)準(zhǔn)庫中定義的 Dictionary<Key, Value> 類型提供了如下語法糖:

[鍵類型 : 值類型]

換句話說,下面兩個聲明是等價的:

let someDictionary: [String: Int] = ["Alex": 31, "Paul": 39]
let someDictionary: Dictionary<String, Int> = ["Alex": 31, "Paul": 39]

上面兩種情況,常量 someDictionary 被聲明為一個字典,其中鍵為 String 類型,值為 Int 類型。

字典中的值可以通過下標(biāo)來訪問,這個下標(biāo)在方括號中指明了具體的鍵:someDictionary["Alex"] 返回鍵 Alex 對應(yīng)的值。如果鍵在字典中不存在的話,則這個下標(biāo)返回 nil。

字典中鍵的類型必須符合 Swift 標(biāo)準(zhǔn)庫中的 Hashable 協(xié)議。

關(guān)于 Swift 標(biāo)準(zhǔn)庫中 Dictionary 類型的詳細(xì)討論,請參閱 字典。

字典類型語法
字典類型[ 類型 : 類型 ]

可選類型

Swift 定義后綴 ? 來作為標(biāo)準(zhǔn)庫中的定義的命名型類型 Optional<Wrapped> 的語法糖。換句話說,下面兩個聲明是等價的:

var optionalInteger: Int?
var optionalInteger: Optional<Int>

在上述兩種情況下,變量 optionalInteger 都被聲明為可選整型類型。注意在類型和 ? 之間沒有空格。

類型 Optional<Wrapped> 是一個枚舉,有兩個成員,nonesome(Wrapped),用來表示可能有也可能沒有的值。任意類型都可以被顯式地聲明(或隱式地轉(zhuǎn)換)為可選類型。如果你在聲明或定義可選變量或?qū)傩缘臅r候沒有提供初始值,它的值則會自動賦為默認(rèn)值 nil。

如果一個可選類型的實(shí)例包含一個值,那么你就可以使用后綴運(yùn)算符 ! 來獲取該值,正如下面描述的:

optionalInteger = 42
optionalInteger! // 42

使用 ! 運(yùn)算符解包值為 nil 的可選值會導(dǎo)致運(yùn)行錯誤。

你也可以使用可選鏈?zhǔn)秸{(diào)用和可選綁定來選擇性地在可選表達(dá)式上執(zhí)行操作。如果值為 nil,不會執(zhí)行任何操作,因此也就沒有運(yùn)行錯誤產(chǎn)生。

更多細(xì)節(jié)以及更多如何使用可選類型的例子,請參閱 可選類型。

可選類型語法
可選類型類型 ?

隱式解析可選類型

當(dāng)可以被訪問時,Swift 語言定義后綴 ! 作為標(biāo)準(zhǔn)庫中命名類型 Optional<Wrapped> 的語法糖,來實(shí)現(xiàn)自動解包的功能。換句話說,下面兩個聲明等價:

var implicitlyUnwrappedString: String!
var explicitlyUnwrappedString: Optional<String>

注意類型與 ! 之間沒有空格。

由于隱式解包修改了包涵其類型的聲明語義,嵌套在元組類型或泛型的可選類型(比如字典元素類型或數(shù)組元素類型),不能被標(biāo)記為隱式解包。例如:

let tupleOfImplicitlyUnwrappedElements: (Int!, Int!)  // 錯誤
let implicitlyUnwrappedTuple: (Int, Int)!             // 正確

let arrayOfImplicitlyUnwrappedElements: [Int!]        // 錯誤
let implicitlyUnwrappedArray: [Int]!                  // 正確

由于隱式解析可選類型和可選類型有同樣的表達(dá)式Optional<Wrapped>,你可以在使用可選類型的地方使用隱式解析可選類型。比如,你可以將隱式解析可選類型的值賦給變量、常量和可選屬性,反之亦然。

正如可選類型一樣,你在聲明隱式解析可選類型的變量或?qū)傩缘臅r候也不用指定初始值,因?yàn)樗心J(rèn)值 nil。

可以使用可選鏈?zhǔn)秸{(diào)用來在隱式解析可選表達(dá)式上選擇性地執(zhí)行操作。如果值為 nil,就不會執(zhí)行任何操作,因此也不會產(chǎn)生運(yùn)行錯誤。

關(guān)于隱式解析可選類型的更多細(xì)節(jié),請參閱 隱式解析可選類型。

隱式解析可選類型語法
隱式解析可選類型類型 !

協(xié)議合成類型

協(xié)議合成類型是一種符合協(xié)議列表中每個指定協(xié)議的類型。協(xié)議合成類型可能會用在類型注解和泛型參數(shù)中。

協(xié)議合成類型的形式如下:

Protocol 1 & Procotol 2

協(xié)議合成類型允許你指定一個值,其類型符合多個協(xié)議的要求且不需要定義一個新的命名型協(xié)議來繼承它想要符合的各個協(xié)議。比如,協(xié)議合成類型 Protocol A & Protocol B & Protocol C 等效于一個從 Protocol A,Protocol B, Protocol C 繼承而來的新協(xié)議 Protocol D,很顯然這樣做有效率的多,甚至不需引入一個新名字。

協(xié)議合成列表中的每項(xiàng)必須是協(xié)議名或協(xié)議合成類型的類型別名。

協(xié)議合成類型語法
</a> 協(xié)議合成類型協(xié)議標(biāo)識符 & 協(xié)議合成延續(xù) 協(xié)議合成延續(xù)協(xié)議標(biāo)識符 | 協(xié)議合成類型 協(xié)議標(biāo)識符類型標(biāo)識符

元類型

元類型是指類型的類型,包括類類型、結(jié)構(gòu)體類型、枚舉類型和協(xié)議類型。

類、結(jié)構(gòu)體或枚舉類型的元類型是相應(yīng)的類型名緊跟 .Type。協(xié)議類型的元類型——并不是運(yùn)行時符合該協(xié)議的具體類型——而是該協(xié)議名字緊跟 .Protocol。比如,類 SomeClass 的元類型就是 SomeClass.Type,協(xié)議 SomeProtocol 的元類型就是 SomeProtocal.Protocol。

你可以使用后綴 self 表達(dá)式來獲取類型。比如,SomeClass.self 返回 SomeClass 本身,而不是 SomeClass 的一個實(shí)例。同樣,SomeProtocol.self 返回 SomeProtocol 本身,而不是運(yùn)行時符合 SomeProtocol 的某個類型的實(shí)例。還可以對類型的實(shí)例使用 type(of:) 表達(dá)式來獲取該實(shí)例在運(yùn)行階段的類型,如下所示:

class SomeBaseClass {
    class func printClassName() {
        println("SomeBaseClass")
    }
}
class SomeSubClass: SomeBaseClass {
    override class func printClassName() {
        println("SomeSubClass")
    }
}
let someInstance: SomeBaseClass = SomeSubClass()
// someInstance 在編譯期是 SomeBaseClass 類型,
// 但是在運(yùn)行期則是 SomeSubClass 類型
type(of: someInstance).printClassName()
// 打印 “SomeSubClass”

可以使用恒等運(yùn)算符(===!==)來測試一個實(shí)例的運(yùn)行時類型和它的編譯時類型是否一致。

if type(of: someInstance) === someInstance.self {
    print("The dynamic and static type of someInstance are the same")
} else {
    print("The dynamic and static type of someInstance are different")
}
// 打印 "The dynamic and static type of someInstance are different"

可以使用初始化表達(dá)式從某個類型的元類型構(gòu)造出一個該類型的實(shí)例。對于類實(shí)例,被調(diào)用的構(gòu)造器必須使用 required 關(guān)鍵字標(biāo)記,或者整個類使用 final 關(guān)鍵字標(biāo)記。

class AnotherSubClass: SomeBaseClass {
    let string: String
    required init(string: String) {
        self.string = string
    }
    override class func printClassName() {
        print("AnotherSubClass")
    }
}
let metatype: AnotherSubClass.Type = AnotherSubClass.self
let anotherInstance = metatype.init(string: "some string")

元類型語法
元類型類型 . Type | 類型 . Protocol

類型繼承子句

類型繼承子句被用來指定一個命名型類型繼承自哪個類、采納哪些協(xié)議。類型繼承子句也用來指定一個類類型專屬協(xié)議。類型繼承子句開始于冒號 :,其后是所需要的類、類型標(biāo)識符列表或兩者都有。

類可以繼承單個超類,采納任意數(shù)量的協(xié)議。當(dāng)定義一個類時,超類的名字必須出現(xiàn)在類型標(biāo)識符列表首位,然后跟上該類需要采納的任意數(shù)量的協(xié)議。如果一個類不是從其它類繼承而來,那么列表可以以協(xié)議開頭。關(guān)于類繼承更多的討論和例子,請參閱 繼承。

其它命名型類型可能只繼承或采納一系列協(xié)議。協(xié)議類型可以繼承自任意數(shù)量的其他協(xié)議。當(dāng)一個協(xié)議類型繼承自其它協(xié)議時,其它協(xié)議中定義的要求會被整合在一起,然后從當(dāng)前協(xié)議繼承的任意類型必須符合所有這些條件。正如在 協(xié)議聲明 中所討論的那樣,可以把 class 關(guān)鍵字放到協(xié)議類型的類型繼承子句的首位,這樣就可以聲明一個類類型專屬協(xié)議。

枚舉定義中的類型繼承子句可以是一系列協(xié)議,或是枚舉的原始值類型的命名型類型。在枚舉定義中使用類型繼承子句來指定原始值類型的例子,請參閱 原始值。

類型繼承子句語法
</a> 類型繼承子句: 類要求 , 類型繼承列表
類型繼承子句: 類要求
類型繼承子句: 類型繼承列表
類型繼承列表類型標(biāo)識符 | 類型標(biāo)識符 , 類型繼承列表
類要求class

類型推斷

Swift 廣泛使用類型推斷,從而允許你省略代碼中很多變量和表達(dá)式的類型或部分類型。比如,對于 var x: Int = 0,你可以完全省略類型而簡寫成 var x = 0,編譯器會正確推斷出 x 的類型 Int。類似的,當(dāng)完整的類型可以從上下文推斷出來時,你也可以省略類型的一部分。比如,如果你寫了 let dict: Dictionary = ["A" : 1],編譯器能推斷出 dict 的類型是 Dictionary<String, Int>。

在上面的兩個例子中,類型信息從表達(dá)式樹的葉子節(jié)點(diǎn)傳向根節(jié)點(diǎn)。也就是說,var x: Int = 0x 的類型首先根據(jù) 0 的類型進(jìn)行推斷,然后將該類型信息傳遞到根節(jié)點(diǎn)(變量 x)。

在 Swift 中,類型信息也可以反方向流動——從根節(jié)點(diǎn)傳向葉子節(jié)點(diǎn)。在下面的例子中,常量 eFloat 上的顯式類型注解(: Float)將導(dǎo)致數(shù)字字面量 2.71828 的類型是 Float 而非 Double。

let e = 2.71828 // e 的類型會被推斷為 Double
let eFloat: Float = 2.71828 // eFloat 的類型為 Float

Swift 中的類型推斷在單獨(dú)的表達(dá)式或語句上進(jìn)行。這意味著所有用于類型推斷的信息必須可以從表達(dá)式或其某個子表達(dá)式的類型檢查中獲取到。

? 詞法結(jié)構(gòu) 表達(dá)式 ?
?