Swift如何使用類(lèi)型擦除及自定義詳解
前言
在 Swift 的世界中,如果我們將協(xié)議稱(chēng)之為國(guó)王,那么泛型則可以視作皇后,所謂一山不容二虎,當(dāng)我們把這兩者結(jié)合起來(lái)使用的時(shí)候,似乎會(huì)遇到極大的困難。那么是否有一種方法,能夠?qū)⑦@兩個(gè)概念結(jié)合在一起,以便讓它們成為我們前進(jìn)道路上的墊腳石,而不是礙手礙腳的呢?答案是有的,這里我們將會(huì)使用到類(lèi)型擦除 (Type Erasure) 這個(gè)強(qiáng)大的特性。
你也許曾聽(tīng)過(guò)類(lèi)型擦除,甚至也使用過(guò)標(biāo)準(zhǔn)庫(kù)提供的類(lèi)型擦除類(lèi)型如 AnySequence。但到底什么是類(lèi)型擦除? 如何自定義類(lèi)型擦除? 在這篇文章中,我將討論如何使用類(lèi)型擦除以及如何自定義。在此感謝 Lorenzo Boaro 提出這個(gè)主題。
有時(shí)你想對(duì)外部調(diào)用者隱藏某個(gè)類(lèi)的具體類(lèi)型,或是一些實(shí)現(xiàn)細(xì)節(jié)。在一些情況下,這樣做能防止靜態(tài)類(lèi)型在項(xiàng)目中濫用,或者保證了類(lèi)型間的交互。類(lèi)型擦除就是移除某個(gè)類(lèi)的具體類(lèi)型使其變得更通用的過(guò)程。
協(xié)議或抽象父類(lèi)可作為類(lèi)型擦除簡(jiǎn)單的實(shí)現(xiàn)方式之一。例如 NSString 就是一個(gè)例子,每次創(chuàng)建一個(gè) NSString 實(shí)例時(shí),這個(gè)對(duì)象并不是一個(gè)普通的 NSString 對(duì)象,它通常是某個(gè)具體的子類(lèi)的實(shí)例,這個(gè)子類(lèi)一般是私有的,同時(shí)這些細(xì)節(jié)通常是被隱藏起來(lái)的。你可以使用子類(lèi)提供的功能而不用知道它具體的類(lèi)型,你也沒(méi)必要將你的代碼與它們的具體類(lèi)型聯(lián)系起來(lái)。
在處理 Swift 泛型以及關(guān)聯(lián)類(lèi)型協(xié)議的時(shí)候,可能需要使用一些高級(jí)的內(nèi)容。Swift 不允許把協(xié)議當(dāng)做具體的類(lèi)型來(lái)使用。例如,如果你想編寫(xiě)一個(gè)方法,它的參數(shù)是一個(gè)包含了 Int 的序列,那么下面這種做法是不正確的:
func f(seq: Sequence<Int>) { ...
你不能這樣使用協(xié)議類(lèi)型,這樣會(huì)在編譯時(shí)報(bào)錯(cuò)。但你可以使用泛型來(lái)替代協(xié)議,解決這個(gè)問(wèn)題:
func f<S: Sequence>(seq: S) where S.Element == Int { ...
有時(shí)候這樣寫(xiě)完全可以,但有些地方還存在一些比較麻煩的情況,通常你不可能只在一個(gè)地方添加泛型: 一個(gè)泛型函數(shù)對(duì)其他泛型要求更多… 更糟糕的是,你不能將泛型作為返回值或者屬性。這就跟我們想的有點(diǎn)不一樣了。
func g<S: Sequence>() -> S where S.Element == Int { ...
我們希望函數(shù) g 能返回任何符合的類(lèi)型,但上面這個(gè)不同,它允許調(diào)用者選擇他所需要的類(lèi)型,然后函數(shù) g 來(lái)提供一個(gè)合適的值。
Swift 標(biāo)準(zhǔn)庫(kù)中提供了 AnySequence 來(lái)幫助我們解決這個(gè)問(wèn)題。AnySequence 包裝了一個(gè)任意類(lèi)型的序列,并擦除了它的類(lèi)型。使用 AnySequence 來(lái)訪(fǎng)問(wèn)這個(gè)序列,我們來(lái)重寫(xiě)一下函數(shù) f 與 函數(shù) g:
func f(seq: AnySequence<Int>) { ...
func g() -> AnySequence<Int> { ...
泛型部分不見(jiàn)了,同時(shí)具體的類(lèi)型也被隱藏起來(lái)了。由于使用了 AnySequence 包裝具體的值,它帶來(lái)了一定的代碼復(fù)雜性以及運(yùn)行時(shí)間成本。但是代碼卻更簡(jiǎn)潔了。
Swift 標(biāo)準(zhǔn)庫(kù)中提供了很多這樣的類(lèi)型,如 AnyCollection、AnyHashable 及 AnyIndex。這些類(lèi)型在你自定義泛型或協(xié)議的時(shí)候非常的管用,你也可以直接使用這些類(lèi)型來(lái)簡(jiǎn)化你的代碼。接下來(lái)讓我們探索實(shí)現(xiàn)類(lèi)型擦除的多種方式吧。
基于類(lèi)的類(lèi)型擦除
有時(shí)我們需要在不暴露類(lèi)型信息的情況下從多個(gè)類(lèi)型中包裝一些公共的功能,這聽(tīng)起來(lái)就像是父類(lèi)-子類(lèi)的關(guān)系。事實(shí)上我們的確可以使用抽象父類(lèi)來(lái)實(shí)現(xiàn)類(lèi)型擦除。父類(lèi)提供 API 接口,不用去管誰(shuí)來(lái)實(shí)現(xiàn)。而子類(lèi)根據(jù)具體的類(lèi)型信息實(shí)現(xiàn)相應(yīng)的功能。
接下來(lái)我們將使用這種方式來(lái)自定義 AnySequence,我們將其命名為 MAnySequence:
class MAnySequence<Element>: Sequence {
這個(gè)類(lèi)需要一個(gè) iterator 類(lèi)型作為 makeIterator 返回類(lèi)型。我們必須要做兩次類(lèi)型擦除來(lái)隱藏底層的序列類(lèi)型以及迭代器的類(lèi)型。我們?cè)?MAnySequence 內(nèi)部定義了一個(gè) Iterator 類(lèi),該類(lèi)遵循著 IteratorProtocol 協(xié)議,并在 next() 方法中使用 fatalError 拋出異常。Swift 本身不支持抽象類(lèi)型,但這樣也夠了:
class Iterator: IteratorProtocol {
func next() -> Element? {
fatalError("Must override next()")
}
}
MAnySequence 對(duì) makeIterator 方法實(shí)現(xiàn)也差不多。直接調(diào)用將拋出異常,這用來(lái)提示子類(lèi)需要重寫(xiě)這個(gè)方法:
func makeIterator() -> Iterator {
fatalError("Must override makeIterator()")
}
}
這樣就定義了一個(gè)基于類(lèi)的類(lèi)型擦除的API,私有的子類(lèi)將來(lái)實(shí)現(xiàn)這些API。公共類(lèi)通過(guò)元素類(lèi)型參數(shù)化,但私有實(shí)現(xiàn)類(lèi)由它包裝的序列類(lèi)型進(jìn)行參數(shù)化:
private class MAnySequenceImpl<Seq: Sequence>: MAnySequence<Seq.Element> {
MAnySequenceImpl 需要一個(gè)繼承于 Iterator 的子類(lèi):
class IteratorImpl: Iterator {
IteratorImpl 包裝了序列的迭代器:
var wrapped: Seq.Iterator
init(_ wrapped: Seq.Iterator) {
self.wrapped = wrapped
}
在 next 方法中調(diào)用被包裝的序列迭代器:
override func next() -> Seq.Element? {
return wrapped.next()
}
}
相似地,MAnySequenceImpl 包裝一個(gè)序列:
var seq: Seq
init(_ seq: Seq) {
self.seq = seq
}
從序列中獲取迭代器,然后將迭代器包裝成 IteratorImpl 對(duì)象返回,這樣就實(shí)現(xiàn)了 makeIterator 的功能。
override func makeIterator() -> IteratorImpl {
return IteratorImpl(seq.makeIterator())
}
}
我們需要一種方法來(lái)實(shí)際創(chuàng)建這些東西:對(duì) MAnySequence 添加一個(gè)靜態(tài)方法,該方法創(chuàng)建一個(gè) MAnySequenceImpl 實(shí)例,并將其作為 MAnySequence 類(lèi)型返回給調(diào)用者。
extension MAnySequence {
static func make<Seq: Sequence>(_ seq: Seq) -> MAnySequence<Element> where Seq.Element == Element {
return MAnySequenceImpl<Seq>(seq)
}
}
在實(shí)際開(kāi)發(fā)中,我們可能會(huì)做一些額外的操作來(lái)讓 MAnySequence 提供一個(gè)初始化方法。
我們來(lái)試試 MAnySequence:
func printInts(_ seq: MAnySequence<Int>) {
for elt in seq {
print(elt)
}
}
let array = [1, 2, 3, 4, 5]
printInts(MAnySequence.make(array))
printInts(MAnySequence.make(array[1 ..< 4]))
完美!
基于函數(shù)的類(lèi)型擦除
有時(shí)我們希望對(duì)外暴露支持多種類(lèi)型的方法,但又不想指定具體的類(lèi)型。一個(gè)簡(jiǎn)單的辦法就是,存儲(chǔ)那些簽名僅涉及到我們想公開(kāi)的類(lèi)型的函數(shù),函數(shù)主體在底層已知具體實(shí)現(xiàn)類(lèi)型的上下文中創(chuàng)建。
我們一起看看如何運(yùn)用這種方法來(lái)設(shè)計(jì) MAnySequence,與前面的實(shí)現(xiàn)很類(lèi)似。它是一個(gè)結(jié)構(gòu)體而非類(lèi),這是因?yàn)樗鼉H僅作為容器使用,不需要有任何的繼承關(guān)系。
struct MAnySequence<Element>: Sequence {
跟之前一樣,MAnySequence 也需要一個(gè)可返回的迭代器(Iterator)。迭代器同樣被設(shè)計(jì)為結(jié)構(gòu)體,并持有一個(gè)參數(shù)為空并返回 Element? 的存儲(chǔ)型屬性,實(shí)際上這個(gè)屬性是一個(gè)函數(shù),被用于 IteratorProtocol 協(xié)議的 next 方法中。接下來(lái) Iterator 遵循 IteratorProtocol 協(xié)議,并在 next 方法中調(diào)用函數(shù):
struct Iterator: IteratorProtocol {
let _next: () -> Element?
func next() -> Element? {
return _next()
}
}
MAnySequence 與 Iterator 很相似:持有一個(gè)參數(shù)為空返回 Iterator 類(lèi)型的存儲(chǔ)型屬性。遵循 Sequence 協(xié)議并在 makeIterator 方法中調(diào)用這個(gè)屬性。
let _makeIterator: () -> Iterator
func makeIterator() -> Iterator {
return _makeIterator()
}
MAnySequence 的構(gòu)造函數(shù)正是魔法起作用的地方,它接收任意序列作為參數(shù):
init<Seq: Sequence>(_ seq: Seq) where Seq.Element == Element {
接下來(lái)需要在構(gòu)造函數(shù)中包裝此序列的功能:
_makeIterator = {
如何生成迭代器?請(qǐng)求 Seq 序列生成:
var iterator = seq.makeIterator()
接下來(lái)我們利用自定義的迭代結(jié)構(gòu)體包裝序列生成的迭代器,包裝后的 _next 屬性將會(huì)在迭代器協(xié)議的 next() 方法中被調(diào)用:
return Iterator(_next: { iterator.next() })
}
}
}
接下來(lái)展示如何使用 MAnySequence:
func printInts(_ seq: MAnySequence<Int>) {
for elt in seq {
print(elt)
}
}
let array = [1, 2, 3, 4, 5]
printInts(MAnySequence(array))
printInts(MAnySequence(array[1 ..< 4]))
正確運(yùn)行,太棒了!
當(dāng)需要將小部分功能包裝為更大類(lèi)型的一部分時(shí),這種基于函數(shù)的類(lèi)型擦除方法特別實(shí)用,這樣做就不需要有單獨(dú)的類(lèi)來(lái)實(shí)現(xiàn)被擦除類(lèi)型的這部分功能了。
比方說(shuō)你現(xiàn)在想要編寫(xiě)一些適用于各種集合類(lèi)型的代碼,但它真正需要能夠?qū)@些集合執(zhí)行的操作是獲取計(jì)數(shù)并執(zhí)行從零開(kāi)始的整數(shù)下標(biāo)。如訪(fǎng)問(wèn) tableView 數(shù)據(jù)源。它可能看起來(lái)像這樣:
class GenericDataSource<Element> {
let count: () -> Int
let getElement: (Int) -> Element
init<C: Collection>(_ c: C) where C.Element == Element,C.Index == Int {
count = { c.count }
getElement = { c[$0 - c.startIndex] }
}
}
GenericDataSource 其他代碼可通過(guò)調(diào)用 count() 或 getElement() 來(lái)操作傳入的集合。且不會(huì)讓集合類(lèi)型破壞 GenericDataSource 泛型參數(shù)。
結(jié)束語(yǔ)
類(lèi)型擦除是一種非常有用的技術(shù),它可用來(lái)阻止泛型對(duì)代碼的侵入,也可用來(lái)保證接口簡(jiǎn)單明了。通過(guò)將底層類(lèi)型包裝起來(lái),將API與具體的功能進(jìn)行拆分。這可以通過(guò)使用抽象的公共超類(lèi)和私有子類(lèi)或?qū)?API 包裝在函數(shù)中來(lái)實(shí)現(xiàn)。對(duì)于只需要一些功能的簡(jiǎn)單情況,基于函數(shù)類(lèi)型擦除極其有效。
Swift 標(biāo)準(zhǔn)庫(kù)提供了幾種可直接利用的類(lèi)型擦除類(lèi)型。如 AnySequence 包裝一個(gè) Sequence,正如其名,AnySequence 允許你對(duì)序列迭代而無(wú)需知道序列具體的類(lèi)型。AnyIterator 也是類(lèi)型擦除的類(lèi)型,它提供一個(gè)類(lèi)型擦除的迭代器。AnyHashable 也同樣是類(lèi)型擦除的類(lèi)型,它提供了對(duì)Hashable類(lèi)型訪(fǎng)問(wèn)功能。Swift 還有很多基于集合的擦除類(lèi)型,你可以通過(guò)搜索 Any 來(lái)查閱。標(biāo)準(zhǔn)庫(kù)中也為 Codable API 設(shè)計(jì)了類(lèi)型擦除類(lèi)型: KeyedEncodingContainer 和 KeyedDecodingContainer。它們都是容器協(xié)議類(lèi)型包裝器,可用來(lái)在不知道底層具體類(lèi)型信息的情況下實(shí)現(xiàn) Encode 和 Decode。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)我們的支持。
作者:Mike Ash,原文鏈接,原文日期:2017-12-18
譯者:rsenjoyer;校對(duì):Yousanflics,numbbbbb;定稿:Forelax
上一篇:Swift中優(yōu)雅處理閉包導(dǎo)致的循環(huán)引用詳解
欄 目:Swift
下一篇:Swift4.1轉(zhuǎn)場(chǎng)動(dòng)畫(huà)實(shí)現(xiàn)側(cè)滑抽屜效果
本文標(biāo)題:Swift如何使用類(lèi)型擦除及自定義詳解
本文地址:http://m.jygsgssxh.com/a1/Swift/11926.html
您可能感興趣的文章
- 01-11swift中defer幾個(gè)簡(jiǎn)單的使用場(chǎng)景詳解
- 01-11Swift利用Decodable解析JSON的一個(gè)小問(wèn)題詳解
- 01-11Swift中defer關(guān)鍵字推遲執(zhí)行示例詳解
- 01-11Swift中初始化init的方法小結(jié)
- 01-11Swift中定義單例的方法實(shí)例
- 01-11Swift利用純代碼實(shí)現(xiàn)時(shí)鐘效果實(shí)例代碼
- 01-11Swift中排序算法的簡(jiǎn)單取舍詳解
- 01-11Swift如何為設(shè)置中心添加常用功能
- 01-11Swift Json實(shí)例詳細(xì)解析
- 01-11Swift利用指紋識(shí)別或面部識(shí)別為應(yīng)用添加私密保護(hù)功能


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹(shù)的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 01-11Swift利用Decodable解析JSON的一個(gè)小問(wèn)題
- 01-11swift中defer幾個(gè)簡(jiǎn)單的使用場(chǎng)景詳解
- 01-11Swift中初始化init的方法小結(jié)
- 01-11Swift中defer關(guān)鍵字推遲執(zhí)行示例詳解
- 01-11Swift利用純代碼實(shí)現(xiàn)時(shí)鐘效果實(shí)例代碼
- 01-11Swift中定義單例的方法實(shí)例
- 01-11Swift中排序算法的簡(jiǎn)單取舍詳解
- 01-11Swift Json實(shí)例詳細(xì)解析
- 01-11Swift如何為設(shè)置中心添加常用功能
- 01-11Swift利用指紋識(shí)別或面部識(shí)別為應(yīng)用添
隨機(jī)閱讀
- 01-10C#中split用法實(shí)例總結(jié)
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 04-02jquery與jsp,用jquery
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 01-10delphi制作wav文件的方法
- 01-10SublimeText編譯C開(kāi)發(fā)環(huán)境設(shè)置
- 01-11Mac OSX 打開(kāi)原生自帶讀寫(xiě)NTFS功能(圖文
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載


