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

類型(Types)


1.0 翻譯:lyuka 校對:numbbbbb, stanzhai

2.0 翻譯+校對:EudeMorgen

2.1 翻譯:mmoaay

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

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

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

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

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

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

類型注解

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

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

在第一個例子中,表達式 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)識符使用點語法(.)來表示在其它模塊或其它類型嵌套內(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ù)表達式轉(zhuǎn)化為閉包,表達式的結(jié)果即閉包返回值。這從語法結(jié)構(gòu)上提供了一種便捷:延遲對表達式的求值,直到其值在函數(shù)體中被使用。以自動閉包做為參數(shù)的函數(shù)類型的例子詳見 自動閉包 。

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

為了指定一個 in-out 參數(shù),可以在參數(shù)類型前加 inout 前綴。但是你不可以對可變長參數(shù)或返回值類型使用 inout。關(guān)于這種參數(shù)的詳細講解請參閱 輸入輸出參數(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ù)類型將從右向左進行組合。例如,函數(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 類型的詳細討論,請參閱 數(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 類型的詳細討論,請參閱 字典。

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

可選類型

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

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

optionalInteger = 42
optionalInteger! // 42

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

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

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

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

隱式解析可選類型

當(dāng)可以被訪問時,Swift 語言定義后綴 ! 作為標(biāo)準(zhǔn)庫中命名類型 Optional<Wrapped> 的語法糖,來實現(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]!                  // 正確

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

正如可選類型一樣,你在聲明隱式解析可選類型的變量或?qū)傩缘臅r候也不用指定初始值,因為它有默認(rèn)值 nil。

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

關(guān)于隱式解析可選類型的更多細節(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é)議名或協(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é)議類型的元類型——并不是運行時符合該協(xié)議的具體類型——而是該協(xié)議名字緊跟 .Protocol。比如,類 SomeClass 的元類型就是 SomeClass.Type,協(xié)議 SomeProtocol 的元類型就是 SomeProtocal.Protocol。

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

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

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

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"

可以使用初始化表達式從某個類型的元類型構(gòu)造出一個該類型的實例。對于類實例,被調(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 廣泛使用類型推斷,從而允許你省略代碼中很多變量和表達式的類型或部分類型。比如,對于 var x: Int = 0,你可以完全省略類型而簡寫成 var x = 0,編譯器會正確推斷出 x 的類型 Int。類似的,當(dāng)完整的類型可以從上下文推斷出來時,你也可以省略類型的一部分。比如,如果你寫了 let dict: Dictionary = ["A" : 1],編譯器能推斷出 dict 的類型是 Dictionary<String, Int>。

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

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

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

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

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