Swift 4.0 編程語言(八)

136.泛型

泛型代碼讓你可以寫出靈活,可重用的函數(shù)和類型,它們可以使用任何類型,受你定義的需求的約束。你可以寫出代碼,避免重復(fù)而且可以用一個(gè)清晰抽象的方式來表達(dá)它的意圖。

泛型是Swift中最有力的特征之一, 而且大部分Swift的標(biāo)準(zhǔn)庫是用泛型代碼建立的。事實(shí)上, 在整個(gè)語言教程中,你一直在使用泛型,盡管你沒有意識到這點(diǎn)。例如, Swift 的數(shù)組和字典類型都是泛型集合。 你可以創(chuàng)建一個(gè)整數(shù)數(shù)組,一個(gè)字符串?dāng)?shù)組,甚至是Swift允許創(chuàng)建的任何類型的數(shù)組。相似的, 你可以創(chuàng)建字典來保存任何指定類型的值, 什么類型并沒有限制。

泛型解決的問題

這里有一個(gè)標(biāo)準(zhǔn)的非泛型的函數(shù) swapTwoInts(::), 用來交換兩個(gè)整數(shù)值:

func swapTwoInts(_ a: inout Int, _ b: inout Int) {

let temporaryA = a

a = b

b = temporaryA

}

這個(gè)函數(shù)用了輸入輸出參數(shù)來交換a和b的值。

swapTwoInts(::) 函數(shù)把b的原始值交換到a, a的原始值到b. 你可以調(diào)用這個(gè)函數(shù)來交換兩個(gè)整型變量中的值:

var someInt = 3

var anotherInt = 107

swapTwoInts(&someInt, &anotherInt)

print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")

// 打印 "someInt is now 107, and anotherInt is now 3"

swapTwoInts(::) 函數(shù)是有用的, 不過只能用于整數(shù)。如果你想交換兩個(gè)字符串, 或者兩個(gè)浮點(diǎn)數(shù), 你就要寫更多的函數(shù), 比如swapTwoStrings(::) 和 swapTwoDoubles(::) 函數(shù):

func swapTwoStrings(_ a: inout String, _ b: inout String) {

let temporaryA = a

a = b

b = temporaryA

}

func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {

let temporaryA = a

a = b

b = temporaryA

}

你可能注意到了,swapTwoInts(::), swapTwoStrings(::), 和 swapTwoDoubles(::) 的函數(shù)體是一樣的。唯一不同的是它們接受的值的類型 (Int, String, 和 Double).

寫一個(gè)可以交換任何類型值的函數(shù),可能更有用和靈活。泛型代碼讓你可以寫出這種函數(shù)。(這些函數(shù)的泛型版本會(huì)在下面定義。)

備注:在所有三個(gè)函數(shù)中, 重要的是a和b的類型要一樣。如果a和b的類型不一樣, 就不可能交換它們兩個(gè)的值。 Swift 是一門類型安全的語言, 不允許把一個(gè)字符串和浮點(diǎn)數(shù)進(jìn)行交換。如果這樣做,會(huì)報(bào)一個(gè)編譯期錯(cuò)誤。

泛型函數(shù)

泛型函數(shù)可以使用任何類型。這里有一個(gè)上面 swapTwoInts(::)函數(shù)的泛型版本 swapTwoValues(::):func swapTwoValues(_ a: inout T, _ b: inout T) {? ? let temporaryA = a? ? a = b? ? b = temporaryA}swapTwoValues(::) 函數(shù)體和 swapTwoInts(::) 函數(shù)體是一樣的。不過, swapTwoValues(::) 函數(shù)第一行跟swapTwoInts(::) 稍微有點(diǎn)不一樣。下面是第一行的比較:func swapTwoInts(_ a: inout Int, _ b: inout Int)func swapTwoValues(_ a: inout T, _ b: inout T)

泛型版本的函數(shù)用了一個(gè)占位符類型名(這里叫T) ,而不是使用實(shí)際的類型名 (比如 Int, String, 或者 Double). 這個(gè)占位符類型名不說T是到底是什么, 但是它表明a和b是同樣的類型 T, 無論T表示什么。每次swapTwoValues(::)函數(shù)調(diào)用的時(shí)候,再?zèng)Q定T是什么類型。

其他的不同是泛型函數(shù)名后面跟著一個(gè)T包括在尖括號中 (). 括號告訴 Swift ,T 在 swapTwoValues(::) 函數(shù)定義中是一個(gè)占位符類型名。因?yàn)?T 是一個(gè)占位符, Swift 不能找到真正的類型 T.

現(xiàn)在可以像調(diào)用swapTwoInts一樣調(diào)用 swapTwoValues(::) 函數(shù), 不過你可以傳入兩個(gè)任何類型的值, 只要兩個(gè)值的類型是一樣的。每次調(diào)用 swapTwoValues(::), T的類型會(huì)從傳入的值的類型推斷出來。

下面兩個(gè)例子里, T 分別推斷為整型和字符串類型:

var someInt = 3

var anotherInt = 107

swapTwoValues(&someInt, &anotherInt)

// someInt is now 107, and anotherInt is now 3

var someString = "hello"

var anotherString = "world"

swapTwoValues(&someString, &anotherString)

// someString is now "world", and anotherString is now "hello"

備注 上面定義的 swapTwoValues(::) 函數(shù)是受到Swift 標(biāo)準(zhǔn)庫函數(shù)swap的啟發(fā)。它可以在你的應(yīng)用里直接使用。如果你需要 swapTwoValues(::) 函數(shù)的功能, 你可以使用 Swift 已存在的 swap(::) 函數(shù)而不用自己重新實(shí)現(xiàn)。

類型參數(shù)

上面的 swapTwoValues(::) 例子, 占位符類型 T 是類型參數(shù)的例子。類型參數(shù)指定和命名一個(gè)占位符類型, 直接寫在函數(shù)名的后面, 在一對尖括號里面 (例如 ).

只要你指定了一個(gè)類型參數(shù), 你就可以用它來定義一個(gè)函數(shù)的參數(shù)類型 (例如 swapTwoValues(::) 函數(shù)里的a和b), 或者作為函數(shù)的返回值類型, 或者在函數(shù)體中用作一個(gè)類型注釋。每種情況下, 函數(shù)調(diào)用時(shí),類型參數(shù)會(huì)被真實(shí)的類型替換。

你可以在尖括號里寫上多個(gè)類型參數(shù),來提供更多的類型參數(shù)。用逗號分開就行。

命名類型參數(shù)

在大多數(shù)情況下, 類型參數(shù)有描述性的名字, 例如 Dictionary 中的Key 和 Value , Array里的Element, 它會(huì)告訴讀者類型參數(shù)和泛型類型或者所在函數(shù)的關(guān)系。不過, 當(dāng)它們之間沒有一個(gè)有意義的關(guān)系時(shí), 通常做法是用單個(gè)字母來給它們命名,比如 T, U, 和 V, 比如上面 swapTwoValues(::) 函數(shù)中的T。

備注 用駝峰式方法給參數(shù)類型命名來表明它們是一個(gè)占位符類型,而不是一個(gè)值。(例如 T 和 MyTypeParameter).

泛型類型

除了泛型函數(shù), Swift 也可以定義泛型類型。它們是可以使用任何類型的類,結(jié)構(gòu)體和枚舉。跟數(shù)組和字典有著相似的方式。

這部分內(nèi)容展示如何寫一個(gè)泛型集合 Stack. 棧是有序集合, 跟數(shù)組類似, 但是操作更加嚴(yán)格。數(shù)組允許任何位置的項(xiàng)的插入和移除。棧只允許在集合尾部添加 (壓棧)。類似的, 棧只允許項(xiàng)目從集合尾部移除 (出棧)。

備注:UINavigationController 使用棧來模擬在導(dǎo)航層次中的視圖控制器。調(diào)用 UINavigationController 的 pushViewController(:animated:) 方法在導(dǎo)航棧上添加一個(gè)視圖控制器, 調(diào)用 popViewControllerAnimated(:) 方法從導(dǎo)航棧上移除一個(gè)視圖控制器。如果你要一個(gè)后進(jìn)先出的方式來管理集合,??梢耘缮嫌脠?。

下面的圖展示了棧的壓棧和出棧的行為:


當(dāng)前棧上有三個(gè)值。第四個(gè)值添加到棧頂。現(xiàn)在棧內(nèi)有四個(gè)值, 最近的值在最上面。

棧頂?shù)闹当灰瞥蛘叱鰲!棾鲆粋€(gè)值后, 棧內(nèi)現(xiàn)在再次是三個(gè)值。

這里有個(gè)非泛型版本的棧, 針對的是整型值的情況:

struct IntStack {

var items = [Int]()

mutating func push(_ item: Int) {

items.append(item)

? ? ?}

mutating func pop() -> Int {

return items.removeLast()

? ?}

}

這個(gè)結(jié)構(gòu)體使用數(shù)組屬性items來保存棧內(nèi)的值。IntStack 提供了兩個(gè)方法, push 和 pop, 用來壓棧和出棧。這兩個(gè)方法都是 mutating, 因?yàn)樗鼈円淖兘Y(jié)構(gòu)體的 items 數(shù)組。IntStack 類型只能用于整數(shù), 不過。如果能定義一個(gè)泛型棧類可能會(huì)更有用, 它可以管理任何類型值。這里是一些代碼的泛型版本:

struct Stack{

var items = [Element]()

mutating func push(_ item: Element) {

items.append(item)

? ? ? }

mutating func pop() -> Element {

return items.removeLast()

? ? ? }

}

注意,事實(shí)上泛型版本的棧和非泛型的版本很像, 只不過有一個(gè)類型參數(shù) Element 取代了實(shí)際類型 Int. 這個(gè)類型名寫在結(jié)構(gòu)體名的后面,放在一對尖括號里面().

Element 是一個(gè)占位符的名字。這個(gè)未來類型可以在結(jié)構(gòu)體定義中作為元素使用。在這種情況下, Element 在三個(gè)地方用作占位符:

創(chuàng)建一個(gè)屬性items, 它是用Element 類型值來初始化的空數(shù)組。

指定 push(_:) 方法有一個(gè)參數(shù) item, 類型是 Element

指定 pop() 方法的返回值,類型是 Element

因?yàn)樗且粋€(gè)泛型類型, Stack可以用來創(chuàng)建Swift中任何有效的類型的棧, 跟字典和數(shù)組的用法類似。

在方括號里寫上棧存儲(chǔ)類型來創(chuàng)建一個(gè)新的 Stack 實(shí)例。例如, 創(chuàng)建一個(gè)字符串的棧, 這樣寫 Stack():

var stackOfStrings = Stack()

stackOfStrings.push("uno")

stackOfStrings.push("dos")

stackOfStrings.push("tres")

stackOfStrings.push("cuatro")

// 這個(gè)?,F(xiàn)在有4個(gè)字符串

下面是壓入四個(gè)值之后stackOfStrings 變化:


從棧中移除一個(gè)值并且返回棧頂?shù)闹? “cuatro”:

let fromTheTop = stackOfStrings.pop()

// fromTheTop 等于 "cuatro", 現(xiàn)在棧內(nèi)有3個(gè)字符串

下面的彈出一個(gè)值后棧的變化:


擴(kuò)展泛型類型

當(dāng)你擴(kuò)展一個(gè)泛型類型, 你不需要在擴(kuò)展定義中提供一個(gè)類型參數(shù)列表。相反, 原類型定義的類型參數(shù)列表可以在擴(kuò)展內(nèi)部使用, 并且,使用原類型類型參數(shù)名在原來的定義中調(diào)用類型參數(shù)。

下面的例子擴(kuò)展了泛型 Stack 類型,添加了一個(gè)只讀計(jì)算屬性 topItem, 它返回棧頂元素,而且不用彈出這個(gè)元素:

extension Stack {

var topItem: Element? {

return items.isEmpty ? nil : items[items.count - 1]

? ?}

}

topItem 屬性返回可選的Element 類型值。如果棧是空的, topItem 返回nil; 如果棧非空, topItem 返回items數(shù)組最后一個(gè)值。

注意,這個(gè)擴(kuò)展沒有定義類型參數(shù)列表。相反, Stack 類型已存在的類型參數(shù)名, Element, 在擴(kuò)展內(nèi)部使用來表示topItem 是一個(gè)可選類型。

計(jì)算屬性topItem 現(xiàn)在可以使用 Stack 實(shí)例來訪問棧頂?shù)脑?而不用去移除它:

if let topItem = stackOfStrings.topItem {

print("The top item on the stack is \(topItem).")

}

// 打印 "The top item on the stack is tres."

類型限制

swapTwoValues(::) 函數(shù)和 Stack 類型可以使用任意類型。不過, 有時(shí)候?qū)κ褂梅盒秃瘮?shù)和類型的類型使用限制是有用的。 類型限制指定一個(gè)類型參數(shù)必須繼承自一個(gè)類,或者符合一個(gè)協(xié)議或者協(xié)議組合。

例如, Swift的字典類型對可以用作鍵的類型進(jìn)行了限制, 字典的鍵類型必須是可哈希的。就是說, 它必須提供一個(gè)方法讓自己獨(dú)一無二。 字典需要鍵可哈希,為了判斷特定鍵是否包含了一個(gè)值。如果沒有這個(gè)要求, 字典就不能判斷是否可以對一個(gè)鍵插入或者修改一個(gè)值。也不能通過給定的鍵找到一個(gè)值。

字典的鍵類型,這個(gè)需求是類型限制強(qiáng)制的。它規(guī)定鍵類型必須符合 Hashable 協(xié)議, 它定義在Swift 標(biāo)準(zhǔn)庫中。Swift 的所有基本類型默認(rèn)都是可哈希的。

創(chuàng)建泛型類型時(shí),你可以定義自己的類型限制。這些限制提供泛型編程大部分能力。諸如哈希特性類型的抽象概念,依據(jù)的是它們概念性的特征而不是它們的顯式類型。

可續(xù)限制語法

通過在類型參數(shù)名后放置一個(gè)單獨(dú)的類或者協(xié)議,然后用冒號分開,來寫類型限制。泛型函數(shù)的類型限制的基本語法顯示如下:func someFunction(someT: T, someU: U) {

// function body goes here

}

上面的假想函數(shù)有兩個(gè)參數(shù)。第一個(gè)類型參數(shù), T, 類型限制是要求T是 SomeClass 的子類。第二個(gè)類型參數(shù), U, 類型限制是要求U符合 SomeProtocol 協(xié)議。

類型限制的行為

這里有一個(gè)非泛型的函數(shù)findIndex(ofString:in:), 它有一個(gè)查找的字符串值和待查找的字符串?dāng)?shù)組。findIndex(ofString:in:) 函數(shù)返回一個(gè)可選的整數(shù)值。它是數(shù)組中第一個(gè)匹配字符串的索引, 如果找不到就返回nil:

func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {

for (index, value) in array.enumerated() {

if value == valueToFind {

return index

? ?}

? ?}

return nil

}

findIndex(ofString:in:) 函數(shù)可以用來在字符串?dāng)?shù)組查找一個(gè)字符串:

let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]

if let foundIndex = findIndex(ofString: "llama", in: strings) {

print("The index of llama is \(foundIndex)")

}

// 打印 "The index of llama is 2"

這種查找字符串的方式只對字符串有用, 不過。 你可以寫一個(gè)泛型函數(shù)來處理其他類型。這里有一個(gè)你期待的泛型版本的 findIndex(ofString:in:)函數(shù), 叫 findIndex(of:in:). 注意,函數(shù)返回值仍然是 Int?, 因?yàn)楹瘮?shù)返回的是可選的索引值, 不是來自數(shù)組的可選值。 不過這個(gè)函數(shù)不能編譯, 原因在這個(gè)例子后面再解釋:

func findIndex(of valueToFind: T, in array:[T]) -> Int? {

for (index, value) in array.enumerated() {

if value == valueToFind {

return index

? ? ?}

? ? }

return nil

}

上面的函數(shù)不能編譯。問題在于等號判斷, “if value == valueToFind”. 不是所有在Swift中的類型都可以用等號進(jìn)行比較的。如果你創(chuàng)建自己的類或者結(jié)構(gòu)體來表示一個(gè)復(fù)雜的數(shù)據(jù)模型, 這個(gè)類或者結(jié)構(gòu)體‘等于’的意思不是Swift能夠理解的。因?yàn)檫@個(gè)原因, 它不能保證這個(gè)代碼對各種可能的T類型都有效, 當(dāng)你嘗試編譯這個(gè)代碼的時(shí)候,就會(huì)報(bào)錯(cuò)。

不過,沒有任何損失。Swift 標(biāo)準(zhǔn)庫定義了一個(gè)協(xié)議 Equatable, 它要求符合類型實(shí)現(xiàn)等于和不等于,來比較這個(gè)類型的任意兩個(gè)值。所有 Swift 的標(biāo)準(zhǔn)類型都自動(dòng)支持這個(gè)協(xié)議。

任何可以比較的類型都可以安全的使用 findIndex(of:in:) 函數(shù), 因?yàn)樗WC支持等于運(yùn)算符。為了說明這個(gè)事實(shí), 在你定義函數(shù)時(shí),你可以在類型參數(shù)定義的時(shí)候?qū)懸粋€(gè)Equatable類型限制:

func findIndex(of valueToFind: T, in array:[T]) -> Int? {

for (index, value) in array.enumerated() {

if value == valueToFind {

return index

? ?}

? }

return nil

}

findIndex(of:in:) 的參數(shù)類型寫作 T: Equatable, 意思是 “符合Equatable 協(xié)議的任意 T 類型?!?br>

限制 findIndex(of:in:) 函數(shù)編譯成功了,然后可以使用任意可以比較的類型, 例如 Double 或者 String:

let doubleIndex = findIndex(of: 9.3, in: [3.14159, 0.1, 0.25])

// doubleIndex is an optional Int with no value, because 9.3 is not in the array

let stringIndex = findIndex(of: "Andrea", in: ["Mike", "Malcolm", "Andrea"])

// stringIndex is an optional Int containing a value of 2

關(guān)聯(lián)類型

當(dāng)定義一個(gè)協(xié)議的時(shí)候, 有時(shí)候聲明一個(gè)或者多個(gè)關(guān)聯(lián)類型是很有用的。一個(gè)關(guān)聯(lián)類型給一個(gè)類型提供一個(gè)占位符名。關(guān)聯(lián)類型使用的實(shí)際類型只要協(xié)議被采用才會(huì)指定。關(guān)聯(lián)類型使用associatedtype 關(guān)鍵字來指定。

關(guān)聯(lián)類型的行為

這里有個(gè)Container協(xié)議的例子, 它聲明了一個(gè)關(guān)聯(lián)類型 ItemType:

protocol Container {

associatedtype ItemType

mutating func append(_ item: ItemType)

var count: Int { get }

subscript(i: Int) -> ItemType { get }

}

Container 協(xié)議定義了任何容器必須提供的三個(gè)必須的能力:

它必須要可以用append(_:)方法給容器添加新項(xiàng)目。

它必須可以通過count屬性訪問容器中項(xiàng)目的數(shù)量。

它必須可以通過下標(biāo)運(yùn)算獲取到容器的每一項(xiàng)。

這個(gè)協(xié)議沒有指定怎么存儲(chǔ)項(xiàng)目或者它允許的類型。這個(gè)協(xié)議只是指定了任何符合類型要提供的三個(gè)功能。一個(gè)符合類型可以提供額外的功能, 只要它滿足三個(gè)必須要求。

任何符合 Container 協(xié)議的類型必須能夠指定它存儲(chǔ)值的類型。特別是, 它必須確保只有正確的類型才可以添加到容器, 它必須清楚下標(biāo)返回的項(xiàng)目的類型。

為了定義這三個(gè)必須要求, Container 協(xié)議需要一個(gè)方法去調(diào)用容器將要裝載的元素類型, 不用知道特定容器類型是什么。Container 協(xié)議需要指定,傳入append(_:) 方法的值必須和容器里的元素類型一樣。容器下標(biāo)返回的值的類型也要和容器里的元素類型一樣。

為了實(shí)現(xiàn)這點(diǎn), Container 協(xié)議定義了一個(gè)關(guān)聯(lián)類型 ItemType, 寫作 associatedtype ItemType. 協(xié)議沒有定義 ItemType是什么—這個(gè)留給符合類型來提供。 盡管如此, ItemType 別名提供了一種方式來調(diào)用容器里的元素類型, 為了使用 append(_:) 方法和下標(biāo)定義了一個(gè)類型。以確保任何容器期望的行為被執(zhí)行。

這里是早前非泛型版本的 IntStack 類型, 采用和符合了 Container 協(xié)議:

struct IntStack: Container {

// original IntStack implementation

var items = [Int]()

mutating func push(_ item: Int) {

items.append(item)

?}

mutating func pop() -> Int {

return items.removeLast()

?}

// conformance to the Container protocol

typealias ItemType = Int

mutating func append(_ item: Int) {

self.push(item)

?}

var count: Int {

return items.count

?}

subscript(i: Int) -> Int {

return items[i]

?}

}

IntStack 類型實(shí)現(xiàn)了 Container 協(xié)議要求的三個(gè)功能。

此外, IntStack 指定,為了實(shí)現(xiàn) Container, 關(guān)聯(lián) ItemType 使用Int類型。typealias ItemType = Int 定義,為Container 協(xié)議的實(shí)現(xiàn),把抽象類型轉(zhuǎn)換為實(shí)際的Int類型。

由于 Swift 的類型推斷, 實(shí)際上你不需要聲明ItemType 為Int. 因?yàn)?IntStack 符合 Container 協(xié)議所有的要求, Swift 可以推斷使用的關(guān)聯(lián)類型 ItemType, 只要簡單查找 append(_:) 方法的參數(shù)類型和下標(biāo)的返回類型。事實(shí)上, 如果你刪除上面的 typealias ItemType = Int, 一切都正常, 因?yàn)樗朗裁搭愋陀糜?ItemType.

你也可以讓你泛型版本的 Stack 類型來符合 Container 協(xié)議:

struct Stack: Container {? ? // original Stackimplementation

var items = [Element]()

mutating func push(_ item: Element) {

items.append(item)

? }

mutating func pop() -> Element {

return items.removeLast()

? }

// conformance to the Container protocol

mutating func append(_ item: Element) {

self.push(item)

? }

var count: Int {

return items.count

? }

subscript(i: Int) -> Element {

return items[i]

? }

}

這一次, 類型參數(shù) Element 用作 append(_:) 方法的參數(shù)類型和下標(biāo)的返回類型。Swift 可以推斷 Element 是關(guān)聯(lián)類型, 在特定容器用作ItemType.

擴(kuò)展存在的類型去指定關(guān)聯(lián)類型

你可以擴(kuò)展存在的類型去符合一個(gè)協(xié)議。這個(gè)包含帶有關(guān)聯(lián)類型的協(xié)議。

Swift 的數(shù)組類型已經(jīng)提供了append(_:)方法, 一個(gè)count 屬性, 和一個(gè)帶有索引獲取元素的下標(biāo)。這三個(gè)能力滿足 Container 協(xié)議的要求。這個(gè)意味著你可以擴(kuò)展數(shù)組來符合 Container 協(xié)議。使用空擴(kuò)展即可實(shí)現(xiàn)這個(gè):

extension Array: Container {}

數(shù)組存在的 append(_:) 方法和下標(biāo)讓 Swift 可以推斷使用ItemType的關(guān)聯(lián)類型, 和上面的泛型 Stack 類型一樣。擴(kuò)展定義后, 你可以把數(shù)組當(dāng)成 Container 使用。

泛型 Where 子句

類型限制, 讓你在使用泛型函數(shù)或者類型時(shí),可以在類型參數(shù)上定義需求。

給關(guān)聯(lián)類型定義需求也是有用的。可以通過定義一個(gè)泛型where子句實(shí)現(xiàn)。 一個(gè)泛型wheare子句,讓你可以要求關(guān)聯(lián)類型必須符合一個(gè)協(xié)議, 或者特定類型參數(shù)和關(guān)聯(lián)類型必須一樣。一個(gè)泛型where子句以where關(guān)鍵字開始, 后面是關(guān)聯(lián)類型的限制或者是類型和關(guān)聯(lián)類型的相等關(guān)系。泛型where子句寫在類型或者函數(shù)體花括號的前面。

下面的例子定義了一個(gè)泛型函數(shù) allItemsMatch, 用來判斷兩個(gè)容器實(shí)例是否有相同順序的相同元素。這個(gè)函數(shù)返回一個(gè)布爾值,如果所有元素都滿足條件就返回 true 否則返回 false.

待比較的兩個(gè)容器不需要是相同類型, 但是它們要有相同類型的元素。通過類型限制的組合跟一個(gè)泛型where子句來表示這第一點(diǎn):

func allItemsMatch(_ someContainer: C1, _ anotherContainer: C2) -> Bool

where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {

// Check that both containers contain the same number of items.

if someContainer.count != anotherContainer.count {

return false

}

// Check each pair of items to see if they are equivalent.

for i in 0..<someContainer.count {

if someContainer[i] != anotherContainer[i] {

return false

}

}

// All items match, so return true.

return true

}

這個(gè)函數(shù)有兩個(gè)參數(shù) someContainer 和 anotherContainer. someContainer 參數(shù)類型是 C1, anotherContainer 參數(shù)類型是 C2. C1 和 C2 是兩個(gè)容器類型的類型參數(shù),在函數(shù)調(diào)用的時(shí)候確定實(shí)際類型。

函數(shù)的兩個(gè)類型參數(shù)要求如下:

C1 必須符合 Container 協(xié)議 (寫作 C1: Container).

C2 也必須符合 Container 協(xié)議 (寫作 C2: Container).

C1 的 ItemType 必須和C2的 ItemType 一樣 (寫作 C1.ItemType == C2.ItemType).

C1的 ItemType 必須符合 Equatable 協(xié)議 (寫作 C1.ItemType: Equatable).

第一第二個(gè)要求定義在函數(shù)的類型參數(shù)列表里, 第三第四的要求定義在函數(shù)的泛型where子句中。

這些要求的意思是:

someContainer 是類型為C1的容器。

anotherContainer 是類型為C2的容器。

someContainer 和 anotherContainer 包含類型相同的元素。

someContainer 中的元素可以用不等于判斷,看它們是否彼此不同。

第三第四個(gè)要求合并意思是, anotherContainer中的元素也可以用不等于判斷, 因?yàn)樗蛃omeContainer 有著相同類型的元素。

allItemsMatch(::) 函數(shù)的這些要求使得它可以用來比較兩個(gè)容器, 即使它們是不同的容器類型。

allItemsMatch(::) 函數(shù)一開始判斷兩個(gè)容器是否含有相同數(shù)量的元素。如果它們包含的元素的個(gè)數(shù)不一樣, 它們就無法比較,函數(shù)返回false.

這個(gè)判斷滿足后, 函數(shù)使用for-in循環(huán)和半開區(qū)間運(yùn)算符遍歷someContainer中所有的元素。對于每個(gè)元素來說, 函數(shù)判斷someContainer 的元素是否不等于anotherContainer 中對應(yīng)的元素。如果兩個(gè)元素不同, 說明兩個(gè)容器不一樣, 函數(shù)返回 false.

如果循環(huán)結(jié)束沒有發(fā)現(xiàn)不匹配, 說明這兩個(gè)容器是匹配的, 函數(shù)返回true.

這里 allItemsMatch(::) 函數(shù)響應(yīng)如下:

var stackOfStrings = Stack()

stackOfStrings.push("uno")

stackOfStrings.push("dos")

stackOfStrings.push("tres")

var arrayOfStrings = ["uno", "dos", "tres"]

if allItemsMatch(stackOfStrings, arrayOfStrings) {

print("All items match.")

} else {

print("Not all items match.")

}

// 打印 "All items match."

上面的例子創(chuàng)建了一個(gè) Stack 實(shí)例來保存字符串, 然后把三個(gè)字符串壓入棧。這個(gè)例子同時(shí)也創(chuàng)建了一個(gè)數(shù)組實(shí)例,它用和棧內(nèi)容一樣的字面量來初始化。盡管棧和數(shù)組是不同的類型, 不過它們都符合 Container 協(xié)議, 然后都包含相同類型的值。所以使用這兩個(gè)容器作為參數(shù),來調(diào)用 allItemsMatch(::) 函數(shù)。在上面的例子里, allItemsMatch(::) 函數(shù)正確的顯示出兩個(gè)容器中的所有元素都是匹配的。

使用泛型 Where 子句擴(kuò)展

你也可以使用泛型 where 子句作為擴(kuò)展的一部分。下面的例子擴(kuò)展上面例子中的泛型棧結(jié)構(gòu), 添加了一個(gè)方法 isTop(_:).

extension Stack where Element: Equatable {

func isTop(_ item: Element) -> Bool {

guard let topItem = items.last else {

return false

? }

return topItem == item

? }

}

新方法 isTop(:) 首先判斷棧不是空, 然后將所給項(xiàng)與棧頂項(xiàng)進(jìn)行比較。如果不使用泛型 where 子句來實(shí)現(xiàn), 你會(huì)有一個(gè)問題: isTop(:) 方法使用了 == 運(yùn)算符, 但是棧的定義沒有要求它的項(xiàng)是 equatable, 所有使用 == 運(yùn)算符會(huì)導(dǎo)致一個(gè)編譯錯(cuò)誤。使用一個(gè)泛型 where 子句讓你可以給擴(kuò)展添加一個(gè)新需求, 這樣擴(kuò)展只有在棧中的項(xiàng)目是 equatable 才會(huì)添加 isTop(_:) 方法。

下面是是 isTop(_:) 方法執(zhí)行的樣子:

if stackOfStrings.isTop("tres") {

print("Top element is tres.")

} else {

print("Top element is something else.")

}

// 打印 "Top element is tres."

如果你嘗試在一個(gè)元素不是等同的棧上調(diào)用 isTop(_:) 方法, 你會(huì)得到一個(gè)編譯錯(cuò)誤。struct NotEquatable { }var notEquatableStack = Stack()

let notEquatableValue = NotEquatable()

notEquatableStack.push(notEquatableValue)

notEquatableStack.isTop(notEquatableValue)? // Error

你可以在擴(kuò)展協(xié)議時(shí)使用泛型 where 子句。下面的例子擴(kuò)展了 Container 協(xié)議, 添加了一個(gè) startsWith(_:) 方法。

extension Container where Item: Equatable {

func startsWith(_ item: Item) -> Bool {

return count >= 1 && self[0] == item

? }

}

startsWith(:) 方法首先確保容器至少有一項(xiàng), 然后判斷容器里的第一項(xiàng)是否匹配所給的項(xiàng)。任何符合 Container 協(xié)議的類型都快要使用這個(gè)新方法 startsWith(:) , 包含棧和數(shù)組, 只要這個(gè)容器的項(xiàng)目是 equatable.

if [9, 9, 9].startsWith(42) {

print("Starts with 42.")

?} else {

print("Starts with something else.")

}

// 打印 "Starts with something else."

上面例子里的泛型 where 子句要求 Item 符合一個(gè)協(xié)議, 不過你可以要求 Item 是一個(gè)特定類型。例如:

extension Container where Item == Double {

func average() -> Double {

var sum = 0.0

for index in 0..<count {

sum += self[index]

? ? ?}

? ? ?return sum / Double(count)

? ? }

}

print([1260.0, 1200.0, 98.6, 37.0].average())

// 打印 "648.9"

這個(gè)例子在容器里添加了一個(gè) average() 方法, 它的 Item 類型是 Double. 它遍歷容器里的項(xiàng), 然后把它們相加, 然后除以容器的項(xiàng)數(shù), 得到平均值。 它顯式把 count 由 Int 轉(zhuǎn)換為 Double.

你可以在一個(gè)泛型 where 子句中包含多個(gè)需求, 每個(gè)需求用逗號分開。

使用泛型 Where 子句關(guān)聯(lián)類型

你可以在一個(gè)關(guān)聯(lián)類型上包含一個(gè)泛型 where 子句。例如, 假設(shè)你想要一個(gè)包含迭代器版本的 Container, 就像使用標(biāo)準(zhǔn)庫里的 Sequence 協(xié)議。這里你可以這樣寫:

protocol Container {

associatedtype Item

mutating func append(_ item: Item)

var count: Int { get }

subscript(i: Int) -> Item { get }

associatedtype Iterator: IteratorProtocol where Iterator.Element == Item

func makeIterator() -> Iterator

}

Iterator 上的泛型 where 子句要求迭代器遍歷相同類型的元素, 并不關(guān)心迭代器的類型。makeIterator() 函數(shù)提供對容器迭代器的訪問。

對于繼承自其他協(xié)議的協(xié)議來說, 在協(xié)議聲明中包含泛型 where 子句, 你可以給繼承來的關(guān)聯(lián)類型添加一個(gè)限制。例如, 下面的代碼, 定義了一個(gè) ComparableContainer 協(xié)議, 它要求 Item 遵守 Comparable 協(xié)議:

protocol ComparableContainer: Container where Item: Comparable { }

泛型下標(biāo)

下標(biāo)也可以是泛型, 它們可以包含泛型 where 子句。在 subscript 后面的尖括號里寫占位符類型名, 然后在下標(biāo)體的大括號前面寫上泛型 where 子句。例如:

extension Container {? ? subscript(indices: Indices) -> [Item]

where Indices.Iterator.Element == Int {

var result = [Item]()

for index in indices {

result.append(self[index])

? ? ? ?}

return result

? ? }

}

這個(gè)擴(kuò)展添加了一個(gè)下標(biāo), 使用一個(gè)索引序列, 然后返回給定索引項(xiàng)目的數(shù)組。泛型下標(biāo)的限制如下:

尖括號里的泛型參數(shù) Indices 必須有相同的類型, 而且要符合 Sequence 協(xié)議。

下標(biāo)只接受一個(gè)參數(shù) indices, 它是 Indices 類型的實(shí)例。

泛型 where 子句要求序列迭代器遍歷的元素是 Int 類型。這確保序列的索引和容器的索引類型一樣。

綜上所述, 這些限制意味著傳給索引參數(shù)的值是一個(gè)整數(shù)序列。

137.訪問控制

訪問控制可以讓限制其他資源文件和模塊訪問你的代碼塊。這個(gè)特性可以確保你隱藏代碼實(shí)現(xiàn)的細(xì)節(jié)。然后指定一個(gè)首選的接口,通過它可以訪問和使用代碼。

你可以針對單個(gè)類型指定訪問級別 (類, 結(jié)構(gòu)體和枚舉), 像屬于這些類型的屬性,方法,構(gòu)造器和下標(biāo)一樣。協(xié)議可以限制到特定的上下文, 全局常量,變量和函數(shù)也可以。

除了提供多個(gè)級別的訪問控制, 通過為特殊場景提供默認(rèn)訪問級別, Swift 減少指定顯式訪問控制級別的需要。事實(shí)上, 如果你寫的是單一目的的應(yīng)用, 你根本不需要指定顯式訪問控制級別。

備注 你的代碼大部分可以使用訪問控制 (屬性, 類型, 函數(shù)等) ,它們被作為 “entities” 在下面部分引用, 為了簡潔。

模塊和源文件

Swift 的訪問控制模型基于模塊和源文件的概念。

一個(gè)模塊是單獨(dú)的代碼分發(fā)單元—作為單獨(dú)單元構(gòu)建和傳輸?shù)目蚣芑蛘叱绦? 可以用Swift 的import 關(guān)鍵字被其他模塊引入。

在Swift里,用Xcode 構(gòu)建的每個(gè)目標(biāo)都被作為單獨(dú)的模塊。 (例如應(yīng)用的bundle或者框架)。如果你把代碼組合成一個(gè)標(biāo)準(zhǔn)的獨(dú)立框架—通過多個(gè)應(yīng)用封裝和重用這個(gè)代碼—當(dāng)它引入和用于一個(gè)應(yīng)用時(shí), 框架里定義的所有都會(huì)是獨(dú)立模塊的一部分?;蛘弋?dāng)它用在其他框架里的時(shí)候。

Swift里的源文件指的是模塊中的源代碼文件。盡管通常做法是在不同的源文件中定義獨(dú)立的類型。一個(gè)單獨(dú)的源文件可以定義多個(gè)類型,函數(shù)等等。

訪問級別

Swift 為你的代碼實(shí)體提供了五個(gè)不同的訪問級別。這些訪問級別和實(shí)體所在的源文件相關(guān)。同時(shí)也和源文件所屬模塊相關(guān)。

Open 訪問和 public 訪問讓實(shí)體可以在任何定義它們的模塊的源文件中使用, 也可以在引入該定義模塊是其他模塊的源文件中使用。當(dāng)框架指定了pulic接口時(shí),你就可以使用 open 或者 public 訪問。open 和 pulic訪問的不同下面會(huì)描述。

Internal 訪問讓實(shí)體可以在任何定義它們的模塊的源文件中使用, 但是不能在該模塊之外的源文件里使用。當(dāng)定義一個(gè)應(yīng)用的內(nèi)部結(jié)構(gòu)體或者框架的內(nèi)部結(jié)構(gòu)體時(shí),你可以使用internal 訪問。

私有文件訪問只允許實(shí)體在自己定義的源文件中使用。使用私有文件訪問隱藏了某個(gè)功能的實(shí)現(xiàn)細(xì)節(jié)。

私有訪問限制實(shí)體在封閉聲明時(shí)使用。當(dāng)某個(gè)功能實(shí)現(xiàn)細(xì)節(jié)用在單獨(dú)聲明時(shí),使用私有訪問來隱藏這些細(xì)節(jié)。

Open 是最高級別的訪問權(quán)限, 私有訪問是最低級別的訪問權(quán)限。

Open 訪問只適用于類和類的成員, 它跟public 訪問不同之處如下:

帶有public訪問權(quán)限的類, 或者任何更嚴(yán)格的訪問級別, 只能在它們定義的模塊里子類化。

帶有public訪問權(quán)限的類成員, 或者任何更嚴(yán)格的訪問級別, 只能在它們定義的模塊里,被子類重寫。

Open 類可以在它們定義的模塊中被子類化, 引入該模塊的其他任意模塊也可以。

Open 類可以在它們定義的模塊中被子類重寫, 引入該模塊的其他任意模塊也可以。

讓一個(gè)類顯式open表明, 你可以考慮到了來自其他模塊的代碼影響,這個(gè)模塊使用這個(gè)類作為一個(gè)子類。你也相應(yīng)的設(shè)計(jì)了自己的類的代碼。

訪問級別的指導(dǎo)原則

Swift 中的訪問級別遵守統(tǒng)一的指導(dǎo)原則: 實(shí)體不可以定義成另一種低級別的實(shí)體。

例如:

一個(gè) public 變量不能定義成internal, file-private, 或者 private 類型, 因?yàn)檫@個(gè)類型不能像pulic變量一樣到處使用。

一個(gè)函數(shù)不能有比它的參數(shù)類型和返回類型更高的訪問級別。因?yàn)楹瘮?shù)不能用在它的組合類型不適用于周圍代碼的地方。

默認(rèn)訪問級別

如果你沒有指定顯式的訪問級別,所有的代碼中的實(shí)體會(huì)有一個(gè)默認(rèn)的內(nèi)部訪問級別。這樣做的結(jié)果是, 大多數(shù)情況下,你都不需要指定一個(gè)顯式的訪問級別。

單目標(biāo)應(yīng)用的訪問級別

在你寫一個(gè)單目標(biāo)的應(yīng)用的時(shí)候, 你的程序代碼通常自包含在應(yīng)用里,不需要給模塊外部使用。默認(rèn)的內(nèi)部訪問級別已經(jīng)滿足要求。因此, 你無需指定一個(gè)自定義的訪問級別。不過你可能想把部分代碼標(biāo)記成 file private 或者 private,為了因此內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。

框架訪問級別

當(dāng)你開發(fā)一個(gè)框架的時(shí)候, 把對外的接口標(biāo)記為 open 或者 public,這樣它就可以被其他模塊看到和訪問, 比如引入這個(gè)框架的應(yīng)用。 對外公開的接口是框架的API.

備注 框架的所有內(nèi)部實(shí)現(xiàn)細(xì)節(jié)依然可以使用默認(rèn)的內(nèi)部訪問級別, 如果想對框架內(nèi)部其他代碼隱藏實(shí)現(xiàn)細(xì)節(jié),可以標(biāo)記為 private 或者 file. 如果你想讓它成為框架的API,你就需要把實(shí)體標(biāo)記為 open 或者 public.

單元測試目標(biāo)的訪問級別

當(dāng)你用單元測試目標(biāo)寫應(yīng)用的時(shí)候, 你的代碼需要對這個(gè)模塊可用,為了能夠測試。默認(rèn)情況下, 只有標(biāo)記為 open 或者 public 的實(shí)體才可以被其他模塊訪問。不過, 如果你使用@testable屬性為產(chǎn)品模塊標(biāo)記引入聲明并且使用可測試編譯產(chǎn)品模塊,單元測試目標(biāo)可以訪問任意內(nèi)部實(shí)體。

訪問控制語法

通過在實(shí)體前放置 open, public, internal, fileprivate, 或者 privateDefine 修飾符來給實(shí)體定義訪問級別:

public class SomePublicClass {}

internal class SomeInternalClass {}

fileprivate class SomeFilePrivateClass {}

private class SomePrivateClass {}

public var somePublicVariable = 0

internal let someInternalConstant = 0

fileprivate func someFilePrivateFunction() {}

private func somePrivateFunction() {}

除非另有說明, 默認(rèn)訪問級別都是內(nèi)部的。也就說說 SomeInternalClass 和 someInternalConstant 即使不寫訪問級別修飾符, 它們依然有內(nèi)部訪問級別:

class SomeInternalClass {}? ? ? ? ? ? ? // implicitly internal

let someInternalConstant = 0? ? ? ? ? ? // implicitly internal

自定義類型

如果你想給一個(gè)自定義類型指定顯式的訪問級別, 在定義類型的時(shí)候指定。在訪問級別允許的地方,新類型可以隨便使用。例如, 如果你定義了一個(gè) file-private 的類, 這個(gè)類只能用作屬性的類型,函數(shù)的參數(shù)或者返回類型, 而且只能在類定義的源文件中。

一個(gè)類型的訪問控制級別同樣影響這個(gè)類型成員的默認(rèn)訪問級別 (它的屬性,方法,構(gòu)造器和下標(biāo))。如果類型的訪問級別是 private 或者 file private, 它的成員的默認(rèn)訪問級別也將是 private 或者 file private. 如果類型的訪問級別是 internal 或者 public, 它的成員的默認(rèn)訪問級別將會(huì)是 internal.

重要:一個(gè) public 類型默認(rèn)有 internal 成員, 而不是public 成員。如果你要成員也是 public, 你必須顯式標(biāo)記。這個(gè)可以保證發(fā)布的API是你想要發(fā)布的, 避免內(nèi)部使用的代碼作為API發(fā)布的錯(cuò)誤。

public class SomePublicClass {? ? ? ? ? ? ? ? ? // explicitly public class

public var somePublicProperty = 0? ? ? ? ? ? // explicitly public class member

var someInternalProperty = 0? ? ? ? ? ? ? ? // implicitly internal class member

fileprivate func someFilePrivateMethod() {}? // explicitly file-private class member

private func somePrivateMethod() {}? ? ? ? ? // explicitly private class member

}

class SomeInternalClass {? ? ? ? ? ? ? ? ? ? ? // implicitly internal class

var someInternalProperty = 0? ? ? ? ? ? ? ? // implicitly internal class member

fileprivate func someFilePrivateMethod() {}? // explicitly file-private class member

private func somePrivateMethod() {}? ? ? ? ? // explicitly private class member

}

fileprivate class SomeFilePrivateClass {? ? ? ? // explicitly file-private class

func someFilePrivateMethod() {}? ? ? ? ? ? ? // implicitly file-private class member

private func somePrivateMethod() {}? ? ? ? ? // explicitly private class member

}

private class SomePrivateClass {? ? ? ? ? ? ? ? // explicitly private class

func somePrivateMethod() {}? ? ? ? ? ? ? ? ? // implicitly private class member

}

元組類型

元組類型的訪問級別是元組里類型中最嚴(yán)格的那個(gè)。例如, 如果你用兩個(gè)不同的類型組成一個(gè)元組, 一個(gè)用 internal 訪問,另外一個(gè)用 private 訪問, 那么元組的訪問級別會(huì)是 private.

備注:元組不像類,結(jié)構(gòu)體和函數(shù)那樣有獨(dú)立的定義方式。一個(gè)元組類型的訪問級別在定義時(shí)自動(dòng)推斷,不需要顯式指定。

函數(shù)類型

函數(shù)的訪問級別要計(jì)算參數(shù)和返回類型中最嚴(yán)格的。如果函數(shù)計(jì)算的訪問級別不符合上下文的默認(rèn)情況,你就要在定義函數(shù)時(shí)顯式指定。

下面的例子定義了一個(gè)全局函數(shù) someFunction(), 沒有提供一個(gè)特定的訪問級別修飾符。你可能希望函數(shù)有默認(rèn)的 “internal”的訪問級別, 但是情況不是這樣。事實(shí)上, 下面的寫法,someFunction() 將不能編譯:

func someFunction() -> (SomeInternalClass, SomePrivateClass) {

// function implementation goes here

}

函數(shù)的返回值是由兩個(gè)自定義類組合成的元組。一個(gè)類定義成 “internal”, 另外一個(gè)類定義成 “private”. 因?yàn)? 元組類型的訪問級別是 “private” .

因?yàn)檫@個(gè)函數(shù)的返回類型是 private, 為了函數(shù)聲明的有效性,你必須標(biāo)記整個(gè)函數(shù)的訪問級別是 private modifier:

private func someFunction() -> (SomeInternalClass, SomePrivateClass) {

// function implementation goes here

}

用public或者internal 修飾符標(biāo)記 someFunction() 是無效的, 使用默認(rèn)的internal也沒有用, 因?yàn)楹瘮?shù)的 public 或者 internal 用戶可能沒有權(quán)限訪問用在函數(shù)返回類型的私有類。

枚舉類型

枚舉的每個(gè)分支都會(huì)自動(dòng)獲得和枚舉一樣的訪問級別。你不能給單獨(dú)的分支指定訪問級別

在下面的例子里, CompassPoint 枚舉有一個(gè)顯式的訪問級別 “public”. 枚舉的分支 north, south, east, 和 west 的訪問級別因此也是 “public”:

public enum CompassPoint {

case north

case south

case east

case west

}

原始值和關(guān)聯(lián)類型

枚舉中所有原始值和管理類型用到的類型訪問級別至少要和枚舉一樣高。如果枚舉訪問級別是internal,原始值的訪問級別就不能是private.

嵌套類型

在private中定義的嵌套類型訪問級別自動(dòng)為 private. 在 file-private 中定義的嵌套類型訪問級別自動(dòng)為 file private. 在public或者internal中定義的嵌套類型訪問級別自動(dòng)為 internal. 如果想讓在public 中定義的嵌套類型成為public, 你必須顯式聲明。

子類化

你可以子類化任何可以在當(dāng)前上下文中訪問中的類。子類的訪問級別不能高過超類—例如, 不能給internal超類寫一個(gè)public的子類。

除此之外, 你可以重寫在特定上下文可見的類成員 (方法,屬性,構(gòu)造器和下標(biāo))。

重寫的類成員比超類更容易訪問。在下面的例子里, A 是一個(gè) public 類,有一個(gè) file-private 方法 someMethod(). B 是A的子類, 訪問級別是 “internal”. 盡管如此, B 提供了一個(gè)重寫的 someMethod(),它的訪問級別是 “internal”, 比超類版本的方法級別要高:

public class A {

fileprivate func someMethod() {}

}

internal class B: A {

override internal func someMethod() {}

? ?}

甚至,子類成員可以調(diào)用超類成員,即使超類成員的訪問級別低于子類成員, 只要訪問發(fā)生在允許訪問的上下文中:

public class A {

fileprivate func someMethod() {}

? ? }

internal class B: A {

override internal func someMethod() {

super.someMethod()

? ?}

}

因?yàn)槌?A 和子類 B 在同一個(gè)源文件里定義, B 的 someMethod()方法可以有效調(diào)用 super.someMethod().

常量,變量,屬性和下標(biāo)

一個(gè)常量,變量,屬性或?qū)傩圆荒鼙人念愋透?public. 用 private 類型來寫一個(gè)public屬性是無效的。相似的, 一個(gè)下標(biāo)不能比它的索引和返回類型更public.

private var privateInstance = SomePrivateClass()

Getters 和 Setters

常量,變量,屬性和下標(biāo)的Getters 和 setters 自動(dòng)和它們所屬的常量,變量,屬性和下標(biāo)的訪問的級別一樣。

你可以給 setter 比對應(yīng)getter 更低的訪問級別, 來限制變量,屬性或者下標(biāo)讀寫的范圍。通過寫 fileprivate(set), private(set), 或者 internal(set)來指定訪問級別。

備注 這個(gè)規(guī)則適用于存儲(chǔ)屬性和計(jì)算屬性。盡管你沒有為一個(gè)存儲(chǔ)屬性寫顯式的 getter 和 setter, Swift 仍然會(huì)合成一個(gè)隱式的 getter 和 setter, 用來訪問存儲(chǔ)屬性的備份存儲(chǔ)。用 fileprivate(set), private(set), 和 internal(set) 來改變這個(gè)合成setter的訪問級別, 跟計(jì)算屬性的顯式setter使用的方法完全一樣。

下面的例子定義了一個(gè)結(jié)構(gòu)體 TrackedString, 用來跟蹤一個(gè)字符串屬性改變的次數(shù):

struct TrackedString {

private(set) var numberOfEdits = 0

var value: String = "" {

didSet {

numberOfEdits += 1

? ?}

? }

}

TrackedString 結(jié)構(gòu)體定義了一個(gè)存儲(chǔ)字符串屬性 value, 初始值為空。這個(gè)結(jié)構(gòu)體同時(shí)定義了存儲(chǔ)整型屬性 numberOfEdits, 它用來跟蹤value被改變的次數(shù)。通過在value屬性上使用didSet屬性觀察者來實(shí)現(xiàn)跟蹤。每次value屬性設(shè)置新值的時(shí)候,它就把 numberOfEdits 值加1.

TrackedString 結(jié)構(gòu)體和 value 屬性沒有顯式提供訪問級別修飾符, 所以它們默認(rèn)的訪問級別是 internal. 不過, numberOfEdits 屬性的訪問級別標(biāo)記為 private(set),表明這個(gè)屬性的 getter的訪問級別仍然是 internal, 但是這個(gè)屬性只能在結(jié)構(gòu)體實(shí)現(xiàn)的代碼里使用 setter. 這使得 TrackedString 可以在內(nèi)部修改 numberOfEdits 屬性, 但是也表示這個(gè)屬性對于外部代碼來說是只讀的—包括 TrackedString 的擴(kuò)展。

如果你創(chuàng)建一個(gè) TrackedString 實(shí)例然后修改它的字符串 value 值幾次, 你會(huì)看到 numberOfEdits 屬性值隨著變化次數(shù)一起更新:

var stringToEdit = TrackedString()

stringToEdit.value = "This string will be tracked."

stringToEdit.value += " This edit will increment numberOfEdits."

stringToEdit.value += " So will this one."

print("The number of edits is \(stringToEdit.numberOfEdits)")

// 打印 "The number of edits is 3"

盡管你可以在其他源文件查詢 numberOfEdits 屬性的當(dāng)前值, 但是你不能進(jìn)行修改。這個(gè)限制保護(hù)結(jié)構(gòu)體編輯跟蹤功能的實(shí)現(xiàn)細(xì)節(jié)。

如果需要,你可以給getter和setter方法指定顯式的訪問級別。下面的例子把TrackedString 定義成public.因此結(jié)構(gòu)體的成員默認(rèn)的訪問級別是 internal. 你可以設(shè)置 numberOfEdits 屬性的 getter 是 public的, 它的屬性 setter 是 private的, 通過合并 public 和 private(set) 的訪問修飾符:

public struct TrackedString {

public private(set) var numberOfEdits = 0

public var value: String = "" {

didSet {

numberOfEdits += 1

? ?}

? }

public init() {}

}

構(gòu)造器

自定義構(gòu)造器可以指定一個(gè)訪問級別,這個(gè)級別小于或者等于它構(gòu)造的類型。唯一的區(qū)別是必須的構(gòu)造器。一個(gè)必須構(gòu)造器訪問級別必須跟它所屬的類一致。

跟函數(shù)和方法參數(shù)一樣, 構(gòu)造器的參數(shù)類型不能比構(gòu)造器擁有的訪問級別更加私有。

默認(rèn)構(gòu)造器

就像默認(rèn)構(gòu)造器中描述的那樣, Swift 會(huì)自動(dòng)為所有結(jié)構(gòu)體或者基類提供一個(gè)沒有參數(shù)的默認(rèn)構(gòu)造器,這些結(jié)構(gòu)體或者基類給所有屬性提供了默認(rèn)值,但是沒有提供任何的構(gòu)造器。

默認(rèn)構(gòu)造器的訪問級別和它要構(gòu)造的類型是一樣的, 除非這個(gè)類型定義為 public. 對于定義為public的類型來說, 默認(rèn)構(gòu)造器訪問級別是 internal. 在其他模塊使用時(shí),如果你想用無參數(shù)的構(gòu)造器來構(gòu)造 public 類型, 你必須顯式定義一個(gè) public 無參數(shù)構(gòu)造器。

結(jié)構(gòu)體類型的默認(rèn)成員構(gòu)造器

如果結(jié)構(gòu)體的存儲(chǔ)屬性是private的,結(jié)構(gòu)體的默認(rèn)成員構(gòu)造器就是 private的。同樣的, 如果結(jié)構(gòu)體任意一個(gè)存儲(chǔ)屬性是file private, 構(gòu)造器也是 file private. 否則, 構(gòu)造器的訪問級別是 internal.

和上面的默認(rèn)構(gòu)造器一樣, 在其他模塊使用時(shí), 如果你想用一個(gè)成員構(gòu)造器來構(gòu)造一個(gè) public 類型的話, 你必須提供一個(gè)public成員構(gòu)造器。

協(xié)議

如果你想要給一個(gè)協(xié)議類型指定一個(gè)顯式的訪問級別, 就在協(xié)議定義的時(shí)候這么做。這個(gè)可以讓你創(chuàng)建協(xié)議, 這個(gè)協(xié)議只能在某些允許訪問的上下文中采用。

協(xié)議定義中每個(gè)需求的訪問級別和協(xié)議的訪問級別是一樣的。你不能把需求設(shè)置成協(xié)議不支持的訪問級別。這可以保證采用協(xié)議的類型可以看見所有的需求。

備注 如果你定義了一個(gè) public 協(xié)議, 協(xié)議的需求實(shí)現(xiàn)時(shí)要求一個(gè) public 訪問級別。這個(gè)行為不同于其他類型, public 類型定義意味著類型成員的訪問級別是 internal.

協(xié)議繼承

如果定義了一個(gè)新協(xié)議,它繼承自一個(gè)存在的協(xié)議, 新協(xié)議的訪問級別最多和繼承協(xié)議的級別一樣。例如, 已存在的協(xié)議訪問級別是internal, 你寫的新協(xié)議卻是是 public.

協(xié)議一致性

一個(gè)類型可以符合一個(gè)訪問級別比自己低的協(xié)議。例如, 你可以定義一個(gè) public 類型用在其他模塊里。如果它符合一個(gè) internal 協(xié)議,就只能用在 internal 協(xié)議的定義模塊內(nèi)。

一個(gè)類型符合某個(gè)協(xié)議的上下文,訪問級別是這個(gè)類型和協(xié)議中最小的一個(gè)。如果一個(gè)類型是 public, 但是協(xié)議是 internal, 這個(gè)類型的一致性協(xié)議也是 internal.

一個(gè)類型符合一個(gè)協(xié)議或者擴(kuò)展符合一個(gè)協(xié)議,你必須確保類型對協(xié)議需求的實(shí)現(xiàn),至少和類型的一致性協(xié)議有一樣的訪問級別。例如, 如果一個(gè)public 類型符合一個(gè) internal, 這個(gè)類型實(shí)現(xiàn)的協(xié)議需求必須是 “internal”.

備注 在 Swift 里, 跟在 Objective-C里一樣, 協(xié)議一致性是全局的—不可能在同樣的程序里,類型以兩種不同的方式來符合一個(gè)協(xié)議。

擴(kuò)展

你可以在任何訪問權(quán)限的上下文中擴(kuò)展一個(gè)類,結(jié)構(gòu)體或者枚舉。擴(kuò)展中添加的類型成員和被擴(kuò)展類型中聲明的類型成員有著一樣的訪問級別。如果你擴(kuò)展一個(gè) public 或者 internal 類型, 你添加的任何類型成員默認(rèn)訪問級別是 internal. 如果你擴(kuò)展一個(gè) file-private 類型, 你添加的所有類型成員的訪問級別都是file private. 如果你擴(kuò)展一個(gè) private 類型, 你添加的任何類型成員訪問級別都是 private.

另外, 你可以用顯式訪問級別修飾符來標(biāo)記一個(gè)擴(kuò)展,來為定義在擴(kuò)展里的所有的成員設(shè)置一個(gè)新的默認(rèn)訪問屬性。單個(gè)類型成員的擴(kuò)展里依然可以重寫這些新的默認(rèn)級別。

使用擴(kuò)展添加協(xié)議一致性

如果你用擴(kuò)展來添加協(xié)議一致性,你就不能為擴(kuò)展提供一個(gè)顯式的訪問級別修飾符。相反, 協(xié)議自己的訪問級別,通常用來為在擴(kuò)展中實(shí)現(xiàn)的協(xié)議需求提供默認(rèn)訪問級別。

泛型

泛型類型和泛型函數(shù)的訪問級別, 是它們自身的訪問級別和它們的類型參數(shù)的任何類型限制的訪問級別之間最小的那個(gè)。

類型別名

你定義的所有類型別名,因?yàn)樵L問控制的目的,會(huì)被看做是不同的類型。一個(gè)類型別名的訪問級別小于或者等于這個(gè)類型。例如, 一個(gè)private 類型的別名可以是一個(gè) private, file-private, internal, public, 或者 open type的別名, 但是一個(gè) public 類型別名不能是一個(gè) internal, file-private, 或者 private 類型的別名。

備注 這個(gè)規(guī)則也適用于用來滿足協(xié)議一致性的關(guān)聯(lián)類型的類型別名。

138.高級運(yùn)算符

除了基本運(yùn)算符之外, Swift 提供了一些高級運(yùn)算符來進(jìn)行更復(fù)雜的值操作。包括位和位移運(yùn)算符。

跟C的算術(shù)運(yùn)算符不同, Swift 的算術(shù)運(yùn)算符默認(rèn)不會(huì)溢出。溢出會(huì)被捕獲和報(bào)錯(cuò)。 選擇溢出行為, 使用 Swift 的溢出算術(shù)運(yùn)算符, 例如溢出加運(yùn)算符 (&+). 所有溢出算術(shù)運(yùn)算符都是以 (&)開始。

當(dāng)你定義結(jié)構(gòu)體,類和枚舉的時(shí)候, 為這些自定義類型實(shí)現(xiàn)自己的標(biāo)準(zhǔn)Swift運(yùn)算符是很有用的。Swift 讓提供這些實(shí)現(xiàn)變得容易,并且能精確決定每種類型的行為。

你不會(huì)被限定在預(yù)置運(yùn)算符上。Swift 給你足夠的自由,用自定義的優(yōu)先級和指定值,來定義你自己的中綴,前綴,后綴和賦值運(yùn)算符。這些運(yùn)算符的用法和預(yù)置運(yùn)算符一樣, 你甚至可以擴(kuò)展已存在的類型來支持自定義的運(yùn)算符。

位運(yùn)算符

位運(yùn)算符可以操作數(shù)據(jù)結(jié)構(gòu)里的單個(gè)數(shù)據(jù)位。它們通常用于低級別編程, 例如圖形編程和設(shè)備驅(qū)動(dòng)編寫。使用外部資源數(shù)據(jù)時(shí),位運(yùn)算符也很有用, 例如編解碼數(shù)據(jù)。

Swift 支持C中所有的位運(yùn)算符, 描述如下。

位 NOT 運(yùn)算符

位 NOT 運(yùn)算符 (~) 把所有位轉(zhuǎn)換成一個(gè)數(shù):


位 NOT 運(yùn)算符是一個(gè)前綴運(yùn)算符, 直接出現(xiàn)在操作數(shù)的前面, 沒有空格:

let initialBits: UInt8 = 0b00001111

let invertedBits = ~initialBits? // 等于 11110000

UInt8 整數(shù)有8位,可以存儲(chǔ)0到255之間的任何值。這個(gè)例子用二進(jìn)制值00001111來初始化一個(gè) UInt8 整數(shù), 它的前四位全是0,后四位全是1. 它等于十進(jìn)制的 15.

位 NOT 運(yùn)算符用來創(chuàng)建一個(gè)新常量 invertedBits, 它等于 initialBits, 不過所有位都是反轉(zhuǎn)的。0變成1, 1變成0. invertedBits 的值是 11110000, 它等于十進(jìn)制的 240.

位 AND 運(yùn)算符

位 AND 運(yùn)算符 (&) 合并兩個(gè)數(shù)的位。它返回一個(gè)新的數(shù)組,如果兩個(gè)輸入數(shù)的位都是1,這個(gè)新數(shù)的位才是1:


在上面的例子里, firstSixBits 和 lastSixBits 中間四位都是 1. 位 AND 運(yùn)算符合并它們變成 00111100, 它等于十進(jìn)制的 60:

let firstSixBits: UInt8 = 0b11111100

let lastSixBits: UInt8? = 0b00111111

let middleFourBits = firstSixBits & lastSixBits? // 等于 00111100


位 OR 運(yùn)算符

位 OR 運(yùn)算符 (|) 比較兩個(gè)數(shù)的位。如果兩個(gè)數(shù)任意一個(gè)數(shù)位為1,這個(gè)運(yùn)算符返回的數(shù)位就是1:


在上面的例子里, someBits 和 moreBits 不同位設(shè)置為 1. 位 OR 運(yùn)算符合并它們變成 11111110, 它等于一個(gè)無符號十進(jìn)制254:

let someBits: UInt8 = 0b10110010

let moreBits: UInt8 = 0b01011110

let combinedbits = someBits | moreBits? // 等于 11111110

位 XOR 運(yùn)算符

位 XOR 運(yùn)算符, 或者 “異或運(yùn)算符” (^), 比較兩個(gè)數(shù)的位。如果兩個(gè)數(shù)位不同返回1,如果相同則返回0:


let firstBits: UInt8 = 0b00010100

let otherBits: UInt8 = 0b00000101

let outputBits = firstBits ^ otherBits? // 等于 00010001

左右移位運(yùn)算符

左移位運(yùn)算符 (<<) 和右移位運(yùn)算符 (>>) 往左或者往右移動(dòng)數(shù)位, 規(guī)則如下。

左移位和右移位實(shí)際上是乘以或者除以2. 左移一個(gè)整數(shù)1位相當(dāng)于乘以2, 而右移一個(gè)整數(shù)1位相當(dāng)于除以2.

無符號整數(shù)移動(dòng)

無符號整數(shù)位移表現(xiàn)如下:

左移或者右移請求數(shù)量的位。

超出整數(shù)存儲(chǔ)范圍的移位被舍棄。

左移或者右移后,缺失的位用0填充。

這個(gè)方法叫邏輯移位。

下面這個(gè)圖展示了 11111111 << 1 和 11111111 >> 1 的結(jié)果。藍(lán)色數(shù)字是要移動(dòng)的, 灰色數(shù)字是要舍棄的, 橙色的0是填充的:


下面是位移動(dòng)在Swift 代碼里的表現(xiàn):

let shiftBits: UInt8 = 4? // 00000100 in binary

shiftBits << 1? ? ? ? ? ? // 00001000

shiftBits << 2? ? ? ? ? ? // 00010000

shiftBits << 5? ? ? ? ? ? // 10000000

shiftBits << 6? ? ? ? ? ? // 00000000

shiftBits >> 2? ? ? ? ? ? // 00000001

你可以使用位移動(dòng)在其他數(shù)據(jù)類型里進(jìn)行編解碼:

let pink: UInt32 = 0xCC6699

let redComponent = (pink & 0xFF0000) >> 16? ? // redComponent 是 0xCC, 或者 204

let greenComponent = (pink & 0x00FF00) >> 8? // greenComponent 是 0x66, 或者 102

let blueComponent = pink & 0x0000FF? ? ? ? ? // blueComponent 是 0x99, 或者 153

這個(gè)例子使用了一個(gè) UInt32 類型的常量 pink 來保存粉色的CSS顏色值。CSS 顏色值 #CC6699 十六進(jìn)制形式寫作 0xCC6699. 經(jīng)過位移 AND (&)和右移運(yùn)算符(>>)操作, 這個(gè)顏色會(huì)被分解為 red (CC), green (66), 和 blue (99) .

紅色部分通過把數(shù)字 0xCC6699 和 0xFF0000進(jìn)行位AND獲取。 0xFF0000 中的0 “掩藏”了 0xCC6699的第二個(gè)和第三個(gè)字節(jié), 導(dǎo)致 6699 被忽略,只留下 0xCC0000 的結(jié)果。

然后把這個(gè)數(shù)字向右移動(dòng)16位 (>> 16). 十六進(jìn)制數(shù)字每對字母使用8位, 所以向右移動(dòng)16位把 0xCC0000 轉(zhuǎn)化為 0x0000CC. 它等于 0xCC, 它的十進(jìn)制值是 204.

類似的, 綠色部分通過把數(shù)字 0xCC6699 和 0x00FF00進(jìn)行位AND獲取, 它的輸出值是 0x006600. 然后把輸出值向右移動(dòng)8位 0x66, 它的十進(jìn)制值是 102.

最后, 藍(lán)色部分通過把數(shù)字 0xCC6699 和 0x0000FF進(jìn)行位AND獲取, 它的輸出值是 0x000099. 它不需要向右移動(dòng), 因?yàn)?0x000099 已經(jīng)等于 0x99, 它的十進(jìn)制值是 153.

有符號整數(shù)移動(dòng)

有符號整數(shù)的移動(dòng)比無符號的要復(fù)雜, 因?yàn)橛蟹栒麛?shù)是用二進(jìn)制表示的(為了簡單,下面的例子用8位有符號整數(shù)展示, 不過這個(gè)原則適用于任何大小的有符號整數(shù)。)

有符號整數(shù)使用第一個(gè)數(shù)位來表示正負(fù) (標(biāo)志位)。 0表示正數(shù), 1表示負(fù)數(shù)。

剩余位用來存儲(chǔ)實(shí)際的值。正數(shù)的存儲(chǔ)和無符號整數(shù)的方式是一樣的, 從0往上數(shù)。這里是4在Int8中的數(shù)位的形式:


標(biāo)志位是 0 (意思是正數(shù)), 7個(gè)數(shù)值位正好是數(shù)字 4, 用二進(jìn)制符號表示。

負(fù)數(shù)存儲(chǔ)是不同的。它們存儲(chǔ)的值是絕對值減去2的n次方。這里n是數(shù)值位的數(shù)字。一個(gè)8位數(shù)有7個(gè)數(shù)值位, 所以2的7次方, 或者 128.

這里是-4在Int8中數(shù)位的形式 -4:


這次符號位是 1 (意思是負(fù)數(shù)), 七位數(shù)值位值是 124 (128 - 4):

負(fù)數(shù)編碼是一個(gè)二進(jìn)制補(bǔ)碼表示。這似乎不是負(fù)數(shù)的常見表示方法, 但是它有幾個(gè)優(yōu)點(diǎn)。

首先, 你可以把-4加-1, 可以進(jìn)行8位的簡單二進(jìn)制加法 (包括標(biāo)志位), 完成后舍棄不符合8位的:

其次, 二進(jìn)制補(bǔ)碼表示讓你可以像正數(shù)那樣移動(dòng)負(fù)數(shù)的數(shù)位。向左移動(dòng)后依然會(huì)翻倍, 向右移動(dòng)后會(huì)減半。為了實(shí)現(xiàn)這個(gè), 當(dāng)有符號整數(shù)向右移動(dòng)時(shí),使用額外的規(guī)則: 當(dāng)你向右移動(dòng)有符號整數(shù)時(shí), 和無符號整數(shù)規(guī)則一樣, 但是左邊空出來的位要用標(biāo)志位填充, 而不是0.


這個(gè)行為保證有符號整數(shù)向右移動(dòng)后,有相同的標(biāo)志位。 也就是算術(shù)移位。

由于正負(fù)數(shù)存儲(chǔ)的特殊方式, 向右移動(dòng)它們接近于0. 移動(dòng)過程中保持標(biāo)志位不變,意味著負(fù)數(shù)在接近0過程中依然是負(fù)數(shù)。

溢出運(yùn)算符

如果你嘗試向一個(gè)整數(shù)常數(shù)或者變量插入無法保存的值, 默認(rèn)情況下, Swift 會(huì)報(bào)錯(cuò)而不是允許無效值的創(chuàng)建。當(dāng)你使用過大或者過小值的時(shí)候,這個(gè)規(guī)則可以提供額外的安全性。

例如, Int16 整數(shù)范圍是 -32768 到 32767. 嘗試存儲(chǔ)超過這個(gè)范圍的數(shù)字會(huì)導(dǎo)致錯(cuò)誤:

var potentialOverflow = Int16.max

// potentialOverflow equals 32767, which is the maximum value an Int16 can hold

potentialOverflow += 1

// 這個(gè)會(huì)報(bào)錯(cuò)

當(dāng)值變的過大或者過小的時(shí)候,提供錯(cuò)誤處理,在給邊界值條件編碼時(shí),會(huì)更加靈活。

不過, 當(dāng)你特別想要一個(gè)溢出條件來截?cái)嗫捎梦粩?shù)的時(shí)候, 你可以選擇這個(gè)行為而不是觸發(fā)一個(gè)錯(cuò)誤。Swift 提供了三個(gè)算術(shù)溢出運(yùn)算符,來為整數(shù)計(jì)算選擇溢出行為。這些運(yùn)算符都以(&)開始:

溢出加 (&+)

溢出減 (&-)

溢出乘 (&*)

值溢出

負(fù)數(shù)和整數(shù)都可以溢出。

這里有一個(gè)例子,展示當(dāng)一個(gè)無符號整數(shù)在正數(shù)方向溢出時(shí),會(huì)發(fā)生什么, 使用的是溢出加運(yùn)算符 (&+):

var unsignedOverflow = UInt8.max

// unsignedOverflow 等于 255, 它是UInt8可以保存的最大值

unsignedOverflow = unsignedOverflow &+ 1

// unsignedOverflow 現(xiàn)在等于 0

變量 unsignedOverflow 使用UInt8 的最大值初始化nt8 (255, 或者 11111111). 然后使用溢出加運(yùn)算符加1. 這個(gè)讓它的二進(jìn)制表示正好超過UInt8可以保存的最大值,這個(gè)導(dǎo)致了溢出, 如下表所示。溢出加之后這個(gè)值00000000依然在UInt8的界限內(nèi)。


相似的事情會(huì)發(fā)生在無符號數(shù)向負(fù)數(shù)方向的溢出上。下面是使用了溢出減運(yùn)算符的例子:

var unsignedOverflow = UInt8.min

// unsignedOverflow 等于 0, 是UInt8可以保存的最小值

unsignedOverflow = unsignedOverflow &- 1

// unsignedOverflow 現(xiàn)在等于 255

UInt8可以保存的最小值是0, 或者二進(jìn)制 00000000. 如果使用溢出減運(yùn)算符減1, 這個(gè)數(shù)字會(huì)溢出變成 11111111, 或者十進(jìn)制 255 .


溢出也會(huì)發(fā)生在有符號整數(shù)。有符號整數(shù)的加減法以位形式執(zhí)行, 標(biāo)志位也參與加減。

var signedOverflow = Int8.min

// signedOverflow 等于 -128, 是Int8可以保存的最小值

signedOverflow = signedOverflow &- 1

// signedOverflow 現(xiàn)在等于 127

Int8保存的最小值是 -128, 或者二進(jìn)制 10000000. 使用溢出減減1,結(jié)果是 01111111, 它會(huì)切換標(biāo)志位然后得正數(shù) 127, 它是Int8可以保存的最大正數(shù)值。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容