Swift學習筆記 (四十四) 類型

Swift 語⾔存在兩種類型:命名型類型和複合型類型。命名型類型是指定義時可以給定名字的類型。命名型類型包括類、 結構體、

枚舉和協議。比如,一個用戶定義類 MyClass 的實例擁有類型 MyClass 。除了用戶定義的命名型類型, Swift 標準庫也定義了很

多常用的命名型類型,包括那些表示數組、字典和可選值的類型。

那些通常被其它語言認爲是基本或原始的數據型類型,比如表示數字、字符和字符串的類型,實際上就是命名型類型,這些類型

在 Swift 標準庫中是使用結構體來定義和實現的。因爲它們是命名型類型,因此你可以按照《擴展》和《擴展聲明》中討論的那

樣,聲明一個擴展來增加它們的⾏爲以滿足你程序的需求。

複合型類型是沒有名字的類型,它由 Swift 本身定義。Swift 存在兩種複合型類型:函數類型和元組類型。一個複合型類型可以包

含命名型類型和其它複合型類型。例如,元組類型 (Int, (Int, Int)) 包含兩個元素:第一個是命名型類型 Int ,第二個是另一個複合型

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

Int 。

本節討論 Swift 語言本身定義的類型,並描述 Swift 中的類型推斷⾏爲。

 

類型註解

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

let someTuple: (Double, Double) = (3.14159, 2.71828)

func someFunction(a: Int) { /* ... */ }

在第⼀個例子中,表達式 someTuple 的類型被指定爲 (Double, Double) 。在第⼆個例子中,函數 someFunction 的參數 a 的類型

被指定爲 Int 。

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

 

類型標識符

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

大多數情況下,類型標識符引用的是與之同名的命名型類型。例如類型標識符 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

 

元組類型

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

你可以使用元組類型作爲一個函數的返回類型,這樣就可以使函數返回多個值。你也可以命名元組類型中的元素,然後⽤這些名

字來引用每個元素的值。元素的名字由一個標識符緊跟一個冒號 (:) 組成。《函數和多返回值》章節⾥有一個展示上述特性的例

子。

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

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 。 Void 是空元組類型 () 的別名。

 

函數類型

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

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

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

你可以對參數類型爲 () -> T (其中 T 是任何類型)的函數使用 autoclosure 特性。這會自動將參數表達式轉化爲閉包,表達式的結

果即閉包返回值。這從語法結構上提供了一種便捷:延遲對表達式的求值,直到其值在函數體中被調用。以自動閉包做爲參數的函

數類型的例子詳見《自動閉包》。

函數類型可以擁有一個可變長參數作爲參數類型中的最後一個參數。從語法⻆角度上講,可變長參數由一個基礎類型名字緊隨三

個點( ... )組成,如 Int... 。可變長參數被認爲是一個包含了基礎類型元素的數組。即 Int... 就是 [Int] 。關於使用可變長參數的例

子,請參閱《可變參數》。

爲了指定一個 in-out 參數,可以在參數類型前加 inout 前綴。但是你不可以對可變長參數或返回值類型使用 inout 。關於這種參

數的詳細講解請參閱《輸入輸出參數》。

如果一個函數類型只有一個形式參數而且形式參數的類型是元組類型,那麼元組類型在寫函數類型的時候必須用圓括號括起來。

比如說, ((Int, Int)) -> Void 是接收⼀個元組 (Int, Int) 作爲形式參數並且不返回任何值的函數類型。 與此相對,不加括號的 (Int,

Int) -> Void 是一個接收兩個 Int 作爲形式參數並且不返回任何值的函數類型。相似地,因爲 Void 是空元組類型 () 的別名,函數

類型 (Void)-> Void 與 (()) -> () 是一樣的 - 一個將空元組作爲唯一參數的函數。但這些類型和無變量的函數類型 () -> () 是不一樣

的。

函數和方法中的變量名並不是函數類型的一部分。例如:

func someFunction(left: Int, right: Int) {}

func anotherFunction(left: Int, right: Int) {}

func functionWithDifferentLabels(top: Int, bottom: Int) {}

var f = someFunction // 函數 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 // 錯誤

由於變量量標籤不不是函數類型的⼀一部分,你可以在寫函數類型的時候省略略它們。

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

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

var operation: (Int, Int) -> Int // 正確

如果一個函數類型包涵多個箭頭(->),那麼函數類型將從右向左進⾏組合。例如,函數類型 (Int) -> (Int) -> Int 可以理解爲 (Int) -

> ((Int) -> Int) ,也就是說,該函數類型的參數爲 Int 類型,其返回類型是一個參數類型爲 Int ,返回類型爲 Int 的函數。

函數類型若要拋出錯誤就必須使⽤ throws 關鍵字來標記,若要重拋錯誤則必須使用 rethrows 關鍵字來標記。 throws 關鍵字是

函數類型的一部分,非拋出函數是拋出函數函數的一個子類型。因此,在使用拋出函數的地⽅也可以使用不拋出函數。拋出和重

拋函數的相關描述⻅章節《拋出函數與方法》和《重拋函數與方法》。

 

對非逃逸閉包的限制

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

是參數時,不能作爲參數傳遞到另一個非逃逸閉包函數中。這樣的限制可以讓 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:) 的兩個參數都是函數。它們都沒有標記爲 @escaping , 因此它們都是非逃逸

的。

上述例子里的被標記爲“錯誤”的四個函數調用會產生編譯錯誤。因爲參數 first 和 second 是非逃逸函數,它們不能夠作爲參數被

傳遞到另一個非閉包函數。相對的, 標記“正確”的兩個函數不會產生編譯錯誤。這些函數調用不會違反限制,因爲 external 不是 

takesTwoFunctions(first:second:) 的參數之一。

如果你需要避免這個限制,標記其中之一的參數爲逃逸,或者使用 withoutActuallyEscaping(_:do:) 函數臨時地轉換非逃逸函數的

其中一個參數爲逃逸函數。關於避免內存訪問衝突,可以參閱《內存安全》。

 

數組類型

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

[ 類型 ]

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

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

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

上⾯兩種情況下,常量 someArray 都被聲明爲字符串數組。數組的元素也可以通過下標訪問: someArray[0] 是指第 0 個元素 

"Alex" 。

你也可以嵌套多對方括號來創建多維數組,最⾥面的方括號中指明數組元素的基本類型。比如,下面例子中使用三對方括號創建

三維整數數組:

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

訪問一個多維數組的元素時,最左邊的下標指向最外層數組的相應位置元素。接下來往右的下標指向第一層嵌入的相應位置元

素,依次類推。這就意味着,在上面的例子中, array3D[0] 是 [[1, 2], [3, 4]] , array3D[0][1] 是 [3, 4] , array3D[0][1][1] 則是 

4 。

 

字典類型

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 協議。

 

可選類型

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

var optionalInteger: Int?

var optionalInteger: Optional<Int>

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

類型 Optional<Wrapped> 是一個枚舉,有兩個成員, none 和 some(Wrapped) (不透明類型),⽤來表示可能有也可能沒有的

值。任意類型都可以被顯式地聲明(或隱式地轉換)爲可選類型。如果你在聲明可選變量或屬性的時候沒有提供初始值, 它的值則

會自動賦爲默認值 nil 。

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

optionalInteger = 42

optionalInteger! // 42

使⽤ ! 運算符解包值爲 nil 的可選值會導致運⾏時錯誤。 你也可以使用可選鏈式調用和可選綁定來選擇性地在可選表達式上執行

操作。如果值爲 nil ,不會執⾏任何操作,因此也就沒有運⾏時錯誤產生。

 

隱式解析可選類型

當可以被訪問時,Swift 語言定義後綴 ! 作爲標準庫中命名類型 Optional<Wrapped> 的語法糖,來實現自動解包的功能。如果嘗

試對一個值爲 nil 的可選類型進⾏隱式解包,將會產生運⾏時錯誤。因爲隱式解包,下⾯兩個聲明等價:

var implicitlyUnwrappedString: String!

var explicitlyUnwrappedString: Optional<String>

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

由於隱式解包會更改包含該類型的聲明語義,嵌套在元組類型或泛型中的可選類型(比如字典元素類型或數組元素類型), 不能被

標記爲隱式解包。例如:

let tupleOfImplicitlyUnwrappedElements: (Int!, Int!)     // 錯誤

let implicitlyUnwrappedTuple: (Int, Int)!     // 正確

let arrayOfImplicitlyUnwrappedElements: [Int!]     // 錯誤

let implicitlyUnwrappedArray: [Int]!   // 正確

由於隱式解析可選類型和可選類型有同樣的類型 Optional<Wrapped> ,你可以在所有使用可選類型的地方使用隱式解析可選類

型。比如,你可以將隱式解析可選類型的值賦給變量、常量和可選屬性,反之亦然。

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

可以使用可選鏈式調用對隱式解析可選表達式選擇性地執⾏操作。如果值爲 nil ,就不會執⾏任何操作,因此也不會產生運行時

錯誤。

 

協議合成類型

協議合成類型定義了一種遵循協議列表中每個指定協議的類型,或者一個現有類型的子類並遵循協議列表中每個指定協議。協議

合成類型只能用在類型註解、泛型參數子句和泛型 where 子句中指定類型。

協議合成類型的形式如下:

Protocol 1 & Procotol 2

協議合成類型允許你指定一個值,其類型遵循多個協議的要求而不需要定義一個新的命名型協議來繼承它想要符合的各個協議。

比如,協議合成類型 Protocol A & Protocol B & Protocol C 等效於一個從 Protocol A , Protocol B , Protocol C 繼承而來的新協

議。同樣的,你可以使用 SuperClass & ProtocolA 來取代申明一個新的協議作爲 SuperClass 的子類並遵循 ProtocolA 。

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

    1.類名

    2.協議名

    3.一個類型別名,它的潛在類型是一個協議合成類型、一個協議或者一個類

當協議合成類型包含類型別名時,同一個協議可能多次出現在定義中 — 重複被忽略。例如,下面代碼中定義的 PQR 等同於 P &

Q & R 。

typealias PQ = P & Q t

ypealias PQR = PQ & Q & R

 

不透明類型

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

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

不透明類型不能作爲元組類型的一部分或範型類型使用,比如數組元素類型或者可選值的包裝類型。

不透明類型的形式如下:

some constraint

constraint 可以是類類型,協議類型,協議組合類型或者 Any 。值只有當它遵循該協議或者組合協議,或者從該類繼承

的時候,才能作爲這個不透明類型的實例使用。和不透明值交互的代碼只能使用該值定義在 constraint 上的接口。

協議聲明⾥不能包括不透明類型。類不能使⽤不透明類型作爲非 final ⽅法的返回值。

使用不透明類型作爲返回值的函數必須返回單一公用底層類型。返回的類型可以包含函數範型類型參數的一部分。舉個例子,函

數 someFunction<T>() 可以返回類型 T 或者 Dictionary<String,T> 的值。

 

元類型

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

類、結構體或枚舉類型的元類型是相應的類型名緊跟 .Type 。協議類型的元類型——並不是運⾏時遵循該協議的具體類型——是

該協議名字緊跟 .Protocol 。比如,類 SomeClass 的元類型就是 SomeClass.Type ,協議 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”

更多信息可以查看 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")

 

自身類型

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

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

在結構體,類或者枚舉值聲明時, 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:) 函數的結果一樣。使用 Self.someStaticMember 訪問當前類型中的成員和使用 

type(of: self).someStaticMember 是一樣的。

 

類型繼承子句

類型繼承子句被用來指定一個命名型類型繼承自哪個類、採納哪些協議。類型繼承子句開始於冒號 : ,其後是類型標識符列表。

類可以繼承自單個超類,並遵循任意數量的協議。當定義⼀個類時,超類的名字必須出現在類型標識符列表首位,然後跟上該類

需要遵循的任意數量的協議。如果一個類不是從其它類繼承而來,那麼列表可以以協議開頭。關於類繼承更多的討論和例⼦子,

請參閱《繼承》。

其它命名型類型只能繼承自或採納一系列協議。協議類型可以繼承自任意數量的其他協議。當一個協議類型繼承自其它協議時,

其它協議中定義的要求會被整合在一起,然後從當前協議繼承的任意類型必須符合所有這些條件。

枚舉定義中的類型繼承⼦句可以是一系列協議,或者是指定單一的命名類型,此時枚舉爲其用例分配原始值。在枚舉定義中使用

類型繼承子句來指定原始值類型的例子,請參閱《原始值》。

 

類型推斷

Swift 廣泛使用類型推斷,從而允許你省略代碼中很多變量和表達式的類型或部分類型。比如,對於 var x: Int = 0 ,你可以完全

省略類型而簡寫成 var x = 0 ,編譯器會正確推斷出 x 的類型 Int 。類似的,當完整的類型可以從上下文推斷出來時,你也可以省

略類型的一部分。⽐如,如果你寫了 let dict: Dictionary = ["A" : 1] ,編譯器能推斷出 dict 的類型是 Dictionary<String, Int> 。

在上面的兩個例子中,類型信息從表達式樹的葉子節點傳向根節點。也就是說, var x: Int = 0 中 x 的類型首先根據 0 的類型進⾏

推斷,然後將該類型信息傳遞到根節點(變量 x )。

在 Swift 中,類型信息也可以反方向流動——從根節點傳向葉子節點。在下⾯的例子中,常量 eFloat 上的顯式類型註解( : Float )

將導致數字字⾯量 2.71828 的類型是 Float ⽽非 Double 。

let e = 2.71828 // e 的類型會被推斷爲 Double

let eFloat: Float = 2.71828 // eFloat 的類型爲 Float

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

型檢查中獲取到。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章