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

類型(Types)

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

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

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

你可以在命名型類型和復合型類型使用小括號。但是在類型旁加小括號沒有任何作用。舉個例子,(Int) 等同于 Int。

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

type

類型語法

類型函數(shù)類型

類型數(shù)組類型

類型字典類型

類型類型標識

類型元組類型

類型可選類型

類型隱式解析可選類型

類型協(xié)議合成類型

類型不透明類型

類型元型類型

類型自身類型

類型Any

類型 類型

類型注解

類型注解顯式地指定一個變量或表達式的類型。類型注解從冒號 (:)開始, 以類型結尾,比如下面兩個例子:

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

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

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

類型注解語法

type-annotation

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

類型標識符

類型標識符可以引用命名型類型,還可引用命名型或復合型類型的別名。

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

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

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

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

var someValue: ExampleModule.MyType

類型標識符語法

type-identifier

類型標識符類型名稱 泛型實參子句可選 | 類型名稱 泛型實參子句可選 . 類型標識符

type-name

類型名稱標識符

元組類型

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

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

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

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

所有的元組類型都包含兩個及以上元素, 除了 VoidVoid 是空元組類型 () 的別名。

元組類型語法

tuple-type

元組類型( ) | ( 元組類型元素 , 元組類型元素列表 )

tuple-type-element-list

元組類型元素列表元組類型元素 | 元組類型元素 , 元組類型元素列表

tuple-type-element

元組類型元素元素名 類型注解 | 類型

element-name

元素名標識符

函數(shù)類型

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

形參類型)->(返回值類型

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

你可以對形參類型為 () -> T(其中 T 是任何類型)的函數(shù)使用 autoclosure 特性,這會在調用側隱式創(chuàng)建一個閉包。這從語法結構上提供了一種便捷:延遲對表達式的求值,直到其值在函數(shù)體中被調用。以自動閉包做為形參的函數(shù)類型的例子詳見 自動閉包。

函數(shù)類型可以擁有一個可變參數(shù)在形參類型中。從語法角度上講,可變參數(shù)由一個基礎類型名字緊隨三個點(...)組成,如 Int...??勺儏?shù)被認為是一個包含了基礎類型元素的數(shù)組。即 Int... 就是 [Int]。關于使用可變參數(shù)的例子,請參閱 可變參數(shù)。

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

如果函數(shù)類型只有一個類型是元組類型的一個形參,那么元組類型在寫函數(shù)類型的時候必須用圓括號括起來。比如說,((Int, Int)) -> Void 是接收一個元組 (Int, Int) 作為形參并且不返回任何值的函數(shù)類型。與此相對,不加括號的 (Int, Int) -> Void 是一個接收兩個 Int 作為形參并且不返回任何值的函數(shù)類型。相似地,因為 Void 是空元組類型 () 的別名,函數(shù)類型 (Void)-> Void(()) -> () 是一樣的 - 一個將空元組作為唯一實參的函數(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) {}
f = functionWithDifferentArgumentTypes     // 錯誤

func functionWithDifferentNumberOfArguments(left: Int, right: Int, top: Int) {}
f = functionWithDifferentNumberOfArguments // 錯誤

由于實參標簽不是函數(shù)類型的一部分,你可以在寫函數(shù)類型的時候省略它們。

var operation: (lhs: Int, rhs: Int) -> Int      // 錯誤
var operation: (_ lhs: Int, _ rhs: Int) -> Int  // 正確
var operation: (Int, Int) -> Int                // 正確

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

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

對非逃逸閉包的限制

當非逃逸閉包函數(shù)是形參時,不能存儲在屬性、變量或任何 Any 類型的常量中,因為這可能導致值的逃逸。

當非逃逸閉包函數(shù)是形參時,不能作為實參傳遞到另一個非逃逸閉包函數(shù)中。這樣的限制可以讓 Swift 在編譯時就完成更好的內存訪問沖突檢查,而不是在運行時。舉個例子:

let external: (Any) -> Void = { _ in () }
func takesTwoFunctions(first: (Any) -> Void, second: (Any) -> Void) {
    first(first)    // 錯誤
    second(second)  // 錯誤

    first(second)   // 錯誤
    second(first)   // 錯誤

    first(external) // 正確
    external(first) // 正確
}

在上面代碼里,takesTwoFunctions(first:second:) 的兩個形參都是函數(shù)。它們都沒有標記為 @escaping, 因此它們都是非逃逸的。

上述例子里的被標記為“錯誤”的四個函數(shù)調用會產生編譯錯誤。因為形參 firstsecond 是非逃逸函數(shù),它們不能夠作為實參被傳遞到另一個非閉包函數(shù)。相對的, 標記“正確”的兩個函數(shù)不會產生編譯錯誤。這些函數(shù)調用不會違反限制,因為 external 不是 takesTwoFunctions(first:second:) 的形參之一。

如果你需要避免這個限制,標記其中一個形參為逃逸,或者使用 withoutActuallyEscaping(_:do:) 函數(shù)臨時轉換其中一個非逃逸函數(shù)形參為逃逸函數(shù)。關于避免內存訪問沖突,可以參閱 內存安全

函數(shù)類型語法

function-type

函數(shù)類型特性列表可選 函數(shù)類型子句 throws可選 -> 類型

function-type-argument-clause

函數(shù)類型子句(- )-
函數(shù)類型子句( 函數(shù)類型實參列表 ...- 可選 )

function-type-argument-list

函數(shù)類型實參列表函數(shù)類型實參 | 函數(shù)類型實參, 函數(shù)類型實參列表

function-type-argument

函數(shù)類型實參特性列表可選 輸入輸出參數(shù)可選 類型 | 實參標簽 類型注解

argument-label

形參標簽標識符

數(shù)組類型

Swift 語言為標準庫中定義的 Array<Element> 類型提供了如下語法糖:

[類型]

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

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

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

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

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

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

關于 Swift 標準庫中 Array 類型的詳細討論,請參閱 數(shù)組。

數(shù)組類型語法

array-type

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

字典類型

Swift 語言為標準庫中定義的 Dictionary<Key, Value> 類型提供了如下語法糖:

[鍵類型 : 值類型]

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

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

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

字典中的值可以通過下標來訪問,這個下標在方括號中指明了具體的鍵:someDictionary["Alex"] 返回鍵 Alex 對應的值。通過下標訪問會獲取對應值的可選類型。如果鍵在字典中不存在的話,則這個下標返回 nil。

字典中鍵的類型必須符合 Swift 標準庫中的 Hashable 協(xié)議。

關于 Swift 標準庫中 Dictionary 類型的詳細討論,請參閱 字典

字典類型語法

dictionary-type

字典類型[ 類型 : 類型 ]

可選類型

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

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

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

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

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

optionalInteger = 42
optionalInteger! // 42

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

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

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

可選類型語法

optional-type

可選類型類型 ?

隱式解析可選類型

當可以被訪問時,Swift 語言定義后綴 ! 作為標準庫中命名類型 Optional<Wrapped> 的語法糖,來實現(xiàn)自動解包的功能。如果嘗試對一個值為 nil 的可選類型進行隱式解包,將會產生運行時錯誤。因為隱式解包,下面兩個聲明等價:

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

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

由于隱式解包會更改包含該類型的聲明語義,嵌套在元組類型或泛型中可選類型(比如字典元素類型或數(shù)組元素類型),不能被標記為隱式解包。例如:

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

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

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

正如可選類型一樣,如果你在聲明隱式解析可選類型的變量或屬性的時候沒有指定初始值,它的值則會自動賦為默認值 nil。

可以使用可選鏈式調用對隱式解析可選表達式選擇性地執(zhí)行操作。如果值為 nil,就不會執(zhí)行任何操作,因此也不會產生運行錯誤。

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

隱式解析可選類型語法

implicitly-unwrapped-optional-type

隱式解析可選類型類型 !

協(xié)議合成類型

協(xié)議合成類型定義了一種遵循協(xié)議列表中每個指定協(xié)議的類型,或者一個現(xiàn)有類型的子類并遵循協(xié)議列表中每個指定協(xié)議。協(xié)議合成類型只能用在類型注解、泛型形參子句和泛型 where 子句中指定類型。

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

Protocol 1 & Procotol 2

協(xié)議合成類型允許你指定一個值,其類型遵循多個協(xié)議的要求而不需要定義一個新的命名型協(xié)議來繼承它想要符合的各個協(xié)議。比如,協(xié)議合成類型 Protocol A & Protocol B & Protocol C 等效于一個從 Protocol A,Protocol B,Protocol C 繼承而來的新協(xié)議。同樣的,你可以使用 SuperClass & ProtocolA 來取代申明一個新的協(xié)議作為 SuperClass 的子類并遵循 ProtocolA。

協(xié)議合成列表中的每一項都必須是下面所列情況之一,列表中最多只能包含一個類:

  • 類名
  • 協(xié)議名
  • 一個類型別名,它的潛在類型是一個協(xié)議合成類型、一個協(xié)議或者一個類

當協(xié)議合成類型包含類型別名時,同一個協(xié)議可能多次出現(xiàn)在定義中 — 重復被忽略。例如,下面代碼中定義的 PQR 等同于 P & Q & R。

typealias PQ = P & Q
typealias PQR = PQ & Q & R

協(xié)議合成類型語法

protocol-composition-type

協(xié)議合成類型協(xié)議標識符 & 協(xié)議合成延續(xù)

protocol-composition-continuation

協(xié)議合成延續(xù)協(xié)議標識符 | 協(xié)議合成類型

不透明類型

不透明類型定義了遵循某個協(xié)議或者合成協(xié)議的類型,但不需要指明底層的具體類型。

不透明類型可以作為函數(shù)或下標的返回值,亦或是屬性的類型使用。

不透明類型不能作為元組類型的一部分或范型類型使用,比如數(shù)組元素類型或者可選值的包裝類型。

不透明類型的形式如下:

some constraint

constraint 可以是類類型,協(xié)議類型,協(xié)議組合類型或者 Any。值只有當它遵循該協(xié)議或者組合協(xié)議,或者從該類繼承的時候,才能作為這個不透明類型的實例使用。和不透明值交互的代碼只能使用該值定義在 constraint 上的接口。

協(xié)議聲明里不能包括不透明類型。類不能使用不透明類型作為非 final 方法的返回值。

使用不透明類型作為返回值的函數(shù)必須返回單一公用底層類型。返回的類型可以包含函數(shù)范型類型形參的一部分。舉個例子,函數(shù) someFunction<T>() 可以返回類型 T 或者 Dictionary<String,T> 的值。

不透明類型語法

opaque-type

不透明類型some type

元類型

元類型是指任意類型的類型,包括類類型、結構體類型、枚舉類型和協(xié)議類型。

類、結構體或枚舉類型的元類型是相應的類型名緊跟 .Type。協(xié)議類型的元類型——并不是運行時遵循該協(xié)議的具體類型——是該協(xié)議名字緊跟 .Protocol。比如,類 SomeClass 的元類型就是 SomeClass.Type,協(xié)議 SomeProtocol 的元類型就是 SomeProtocal.Protocol。

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

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”

更多信息可以查看 Swift 標準庫里的 type(of:)

可以使用初始化表達式從某個類型的元類型構造出一個該類型的實例。對于類實例,被調用的構造器必須使用 required 關鍵字標記,或者整個類使用 final 關鍵字標記。

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")

元類型語法

metatype-type

元類型類型 . Type | 類型 . Protocol

自身類型

Self 類型不是具體的類型,而是讓你更方便的引用當前類型,不需要重復或者知道該類的名字。

在協(xié)議聲明或者協(xié)議成員聲明時,Self 類型引用的是最終遵循該協(xié)議的類型。

在結構體,類或者枚舉值聲明時,Self 類型引用的是聲明的類型。在某個類型成員聲明時,Self 類型引用的是該類型。在類成員聲明時,Self 可以在方法的返回值和方法體中使用,但不能在其他上下文中使用。舉個例子,下面的代碼演示了返回值是 Self 的實例方法 f 。

class Superclass {
    func f() -> Self { return self }
}
let x = Superclass()
print(type(of: x.f()))
// 打印 "Superclass"

class Subclass: Superclass { }
let y = Subclass()
print(type(of: y.f()))
// 打印 "Subclass"

let z: Superclass = Subclass()
print(type(of: z.f()))
// 打印 "Subclass"

上面例子的最后一部分表明 Self 引用的是值 z 的運行時類型 Subclass ,而不是變量本身的編譯時類型 Superclass 。

在嵌套類型聲明時,Self 類型引用的是最內層聲明的類型。

Self 類型引用的類型和 Swift 標準庫中 type(of:) 函數(shù)的結果一樣。使用 Self.someStaticMember 訪問當前類型中的成員和使用 type(of: self).someStaticMember 是一樣的。

自身類型語法

self-type

自身類型Self

類型繼承子句

類型繼承子句被用來指定一個命名型類型繼承自哪個類、采納哪些協(xié)議。類型繼承子句開始于冒號 :,其后是類型標識符列表。

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

其它命名型類型只能繼承自或采納一系列協(xié)議。協(xié)議類型可以繼承自任意數(shù)量的其他協(xié)議。當一個協(xié)議類型繼承自其它協(xié)議時,其它協(xié)議中定義的要求會被整合在一起,然后從當前協(xié)議繼承的任意類型必須符合所有這些條件。

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

類型繼承子句語法

type-inheritance-clause

類型繼承子句: 類型繼承列表

type-inheritance-list

類型繼承列表類型標識符 | 類型標識符 , 類型繼承列表

類型推斷

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

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

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

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

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

? 詞法結構 表達式 ?
?