最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Swift如何使用類型擦除及自定義詳解

 更新時(shí)間:2018年10月12日 09:23:26   作者:WwiftGG  
有很多地方會用到類型擦除,并且它們的作用的各不相同。下面這篇文章主要給大家介紹了關(guān)于Swift如何使用類型擦除及自定義的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下

前言

在 Swift 的世界中,如果我們將協(xié)議稱之為國王,那么泛型則可以視作皇后,所謂一山不容二虎,當(dāng)我們把這兩者結(jié)合起來使用的時(shí)候,似乎會遇到極大的困難。那么是否有一種方法,能夠?qū)⑦@兩個概念結(jié)合在一起,以便讓它們成為我們前進(jìn)道路上的墊腳石,而不是礙手礙腳的呢?答案是有的,這里我們將會使用到類型擦除 (Type Erasure) 這個強(qiáng)大的特性。

你也許曾聽過類型擦除,甚至也使用過標(biāo)準(zhǔn)庫提供的類型擦除類型如 AnySequence。但到底什么是類型擦除? 如何自定義類型擦除? 在這篇文章中,我將討論如何使用類型擦除以及如何自定義。在此感謝 Lorenzo Boaro 提出這個主題。

有時(shí)你想對外部調(diào)用者隱藏某個類的具體類型,或是一些實(shí)現(xiàn)細(xì)節(jié)。在一些情況下,這樣做能防止靜態(tài)類型在項(xiàng)目中濫用,或者保證了類型間的交互。類型擦除就是移除某個類的具體類型使其變得更通用的過程。

協(xié)議或抽象父類可作為類型擦除簡單的實(shí)現(xiàn)方式之一。例如 NSString 就是一個例子,每次創(chuàng)建一個 NSString 實(shí)例時(shí),這個對象并不是一個普通的 NSString 對象,它通常是某個具體的子類的實(shí)例,這個子類一般是私有的,同時(shí)這些細(xì)節(jié)通常是被隱藏起來的。你可以使用子類提供的功能而不用知道它具體的類型,你也沒必要將你的代碼與它們的具體類型聯(lián)系起來。

在處理 Swift 泛型以及關(guān)聯(lián)類型協(xié)議的時(shí)候,可能需要使用一些高級的內(nèi)容。Swift 不允許把協(xié)議當(dāng)做具體的類型來使用。例如,如果你想編寫一個方法,它的參數(shù)是一個包含了 Int 的序列,那么下面這種做法是不正確的:

func f(seq: Sequence<Int>) { ...

你不能這樣使用協(xié)議類型,這樣會在編譯時(shí)報(bào)錯。但你可以使用泛型來替代協(xié)議,解決這個問題:

func f<S: Sequence>(seq: S) where S.Element == Int { ...

有時(shí)候這樣寫完全可以,但有些地方還存在一些比較麻煩的情況,通常你不可能只在一個地方添加泛型: 一個泛型函數(shù)對其他泛型要求更多… 更糟糕的是,你不能將泛型作為返回值或者屬性。這就跟我們想的有點(diǎn)不一樣了。

func g<S: Sequence>() -> S where S.Element == Int { ...

我們希望函數(shù) g 能返回任何符合的類型,但上面這個不同,它允許調(diào)用者選擇他所需要的類型,然后函數(shù) g 來提供一個合適的值。

Swift 標(biāo)準(zhǔn)庫中提供了 AnySequence 來幫助我們解決這個問題。AnySequence 包裝了一個任意類型的序列,并擦除了它的類型。使用 AnySequence 來訪問這個序列,我們來重寫一下函數(shù) f 與 函數(shù) g:

func f(seq: AnySequence<Int>) { ...

func g() -> AnySequence<Int> { ...

泛型部分不見了,同時(shí)具體的類型也被隱藏起來了。由于使用了 AnySequence 包裝具體的值,它帶來了一定的代碼復(fù)雜性以及運(yùn)行時(shí)間成本。但是代碼卻更簡潔了。

Swift 標(biāo)準(zhǔn)庫中提供了很多這樣的類型,如 AnyCollection、AnyHashable 及 AnyIndex。這些類型在你自定義泛型或協(xié)議的時(shí)候非常的管用,你也可以直接使用這些類型來簡化你的代碼。接下來讓我們探索實(shí)現(xiàn)類型擦除的多種方式吧。

基于類的類型擦除

有時(shí)我們需要在不暴露類型信息的情況下從多個類型中包裝一些公共的功能,這聽起來就像是父類-子類的關(guān)系。事實(shí)上我們的確可以使用抽象父類來實(shí)現(xiàn)類型擦除。父類提供 API 接口,不用去管誰來實(shí)現(xiàn)。而子類根據(jù)具體的類型信息實(shí)現(xiàn)相應(yīng)的功能。

接下來我們將使用這種方式來自定義 AnySequence,我們將其命名為 MAnySequence:

class MAnySequence<Element>: Sequence {

這個類需要一個 iterator 類型作為 makeIterator 返回類型。我們必須要做兩次類型擦除來隱藏底層的序列類型以及迭代器的類型。我們在 MAnySequence 內(nèi)部定義了一個 Iterator 類,該類遵循著 IteratorProtocol 協(xié)議,并在 next() 方法中使用 fatalError 拋出異常。Swift 本身不支持抽象類型,但這樣也夠了:

class Iterator: IteratorProtocol {
 func next() -> Element? {
 fatalError("Must override next()")
 }
}

MAnySequence 對 makeIterator 方法實(shí)現(xiàn)也差不多。直接調(diào)用將拋出異常,這用來提示子類需要重寫這個方法:

 func makeIterator() -> Iterator {
 fatalError("Must override makeIterator()")
 }
}

這樣就定義了一個基于類的類型擦除的API,私有的子類將來實(shí)現(xiàn)這些API。公共類通過元素類型參數(shù)化,但私有實(shí)現(xiàn)類由它包裝的序列類型進(jìn)行參數(shù)化:

private class MAnySequenceImpl<Seq: Sequence>: MAnySequence<Seq.Element> {

MAnySequenceImpl 需要一個繼承于 Iterator 的子類:

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 包裝一個序列:

var seq: Seq

init(_ seq: Seq) {
 self.seq = seq
}

從序列中獲取迭代器,然后將迭代器包裝成 IteratorImpl 對象返回,這樣就實(shí)現(xiàn)了 makeIterator 的功能。

 override func makeIterator() -> IteratorImpl {
 return IteratorImpl(seq.makeIterator())
 }

}

我們需要一種方法來實(shí)際創(chuàng)建這些東西:對 MAnySequence 添加一個靜態(tài)方法,該方法創(chuàng)建一個 MAnySequenceImpl 實(shí)例,并將其作為 MAnySequence 類型返回給調(diào)用者。

extension MAnySequence {
 static func make<Seq: Sequence>(_ seq: Seq) -> MAnySequence<Element> where Seq.Element == Element {
 return MAnySequenceImpl<Seq>(seq)
 }
}

在實(shí)際開發(fā)中,我們可能會做一些額外的操作來讓 MAnySequence 提供一個初始化方法。

我們來試試 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ù)的類型擦除

有時(shí)我們希望對外暴露支持多種類型的方法,但又不想指定具體的類型。一個簡單的辦法就是,存儲那些簽名僅涉及到我們想公開的類型的函數(shù),函數(shù)主體在底層已知具體實(shí)現(xiàn)類型的上下文中創(chuàng)建。

我們一起看看如何運(yùn)用這種方法來設(shè)計(jì) MAnySequence,與前面的實(shí)現(xiàn)很類似。它是一個結(jié)構(gòu)體而非類,這是因?yàn)樗鼉H僅作為容器使用,不需要有任何的繼承關(guān)系。

struct MAnySequence<Element>: Sequence {

跟之前一樣,MAnySequence 也需要一個可返回的迭代器(Iterator)。迭代器同樣被設(shè)計(jì)為結(jié)構(gòu)體,并持有一個參數(shù)為空并返回 Element? 的存儲型屬性,實(shí)際上這個屬性是一個函數(shù),被用于 IteratorProtocol 協(xié)議的 next 方法中。接下來 Iterator 遵循 IteratorProtocol 協(xié)議,并在 next 方法中調(diào)用函數(shù):

struct Iterator: IteratorProtocol {
 let _next: () -> Element?

 func next() -> Element? {
  return _next()
 }
}

MAnySequence 與 Iterator 很相似:持有一個參數(shù)為空返回 Iterator 類型的存儲型屬性。遵循 Sequence 協(xié)議并在 makeIterator 方法中調(diào)用這個屬性。

let _makeIterator: () -> Iterator

func makeIterator() -> Iterator {
 return _makeIterator()
}

MAnySequence 的構(gòu)造函數(shù)正是魔法起作用的地方,它接收任意序列作為參數(shù):

init<Seq: Sequence>(_ seq: Seq) where Seq.Element == Element {

接下來需要在構(gòu)造函數(shù)中包裝此序列的功能:

_makeIterator = {

如何生成迭代器?請求 Seq 序列生成:

var iterator = seq.makeIterator()

接下來我們利用自定義的迭代結(jié)構(gòu)體包裝序列生成的迭代器,包裝后的 _next 屬性將會在迭代器協(xié)議的 next() 方法中被調(diào)用:

   return Iterator(_next: { iterator.next() })
  }
 }
}

接下來展示如何使用 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)需要將小部分功能包裝為更大類型的一部分時(shí),這種基于函數(shù)的類型擦除方法特別實(shí)用,這樣做就不需要有單獨(dú)的類來實(shí)現(xiàn)被擦除類型的這部分功能了。

比方說你現(xiàn)在想要編寫一些適用于各種集合類型的代碼,但它真正需要能夠?qū)@些集合執(zhí)行的操作是獲取計(jì)數(shù)并執(zhí)行從零開始的整數(shù)下標(biāo)。如訪問 tableView 數(shù)據(jù)源。它可能看起來像這樣:

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 其他代碼可通過調(diào)用 count() 或 getElement() 來操作傳入的集合。且不會讓集合類型破壞 GenericDataSource 泛型參數(shù)。

結(jié)束語

類型擦除是一種非常有用的技術(shù),它可用來阻止泛型對代碼的侵入,也可用來保證接口簡單明了。通過將底層類型包裝起來,將API與具體的功能進(jìn)行拆分。這可以通過使用抽象的公共超類和私有子類或?qū)?API 包裝在函數(shù)中來實(shí)現(xiàn)。對于只需要一些功能的簡單情況,基于函數(shù)類型擦除極其有效。

Swift 標(biāo)準(zhǔn)庫提供了幾種可直接利用的類型擦除類型。如 AnySequence 包裝一個 Sequence,正如其名,AnySequence 允許你對序列迭代而無需知道序列具體的類型。AnyIterator 也是類型擦除的類型,它提供一個類型擦除的迭代器。AnyHashable 也同樣是類型擦除的類型,它提供了對Hashable類型訪問功能。Swift 還有很多基于集合的擦除類型,你可以通過搜索 Any 來查閱。標(biāo)準(zhǔn)庫中也為 Codable API 設(shè)計(jì)了類型擦除類型: KeyedEncodingContainer 和 KeyedDecodingContainer。它們都是容器協(xié)議類型包裝器,可用來在不知道底層具體類型信息的情況下實(shí)現(xiàn) Encode 和 Decode。

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。

作者:Mike Ash,原文鏈接,原文日期:2017-12-18

譯者:rsenjoyer;校對:Yousanflics,numbbbbb;定稿:Forelax

相關(guān)文章

  • Swift與C語言指針結(jié)合使用實(shí)例

    Swift與C語言指針結(jié)合使用實(shí)例

    這篇文章主要介紹了Swift與C語言指針結(jié)合使用實(shí)例,本文講解了用以輸入/輸出的參數(shù)指針、作為數(shù)組使用的參數(shù)指針、用作字符串參數(shù)的指針、指針參數(shù)轉(zhuǎn)換的安全性等內(nèi)容,需要的朋友可以參考下
    2015-05-05
  • 詳解Swift中的函數(shù)及函數(shù)閉包使用

    詳解Swift中的函數(shù)及函數(shù)閉包使用

    Swift的函數(shù)在創(chuàng)建和調(diào)用時(shí)非常簡潔,在編寫具有閉包特性的函數(shù)時(shí)同樣也相當(dāng)方便,以下我們就來詳解Swift中的函數(shù)及函數(shù)閉包使用:
    2016-06-06
  • Swift中的可選項(xiàng)Optional解包方式實(shí)現(xiàn)原理

    Swift中的可選項(xiàng)Optional解包方式實(shí)現(xiàn)原理

    這篇文章主要為大家介紹了Swift中的可選項(xiàng)Optional解包方式實(shí)現(xiàn)原理示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • Swift讀取App的版本信息與PCH文件詳解

    Swift讀取App的版本信息與PCH文件詳解

    這篇文章主要介紹了Swift讀取App的版本信息與PCH文件的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),對大家具有一定的參考價(jià)值,需要的朋友們下面來一起看看吧。
    2017-03-03
  • Swift代碼自定義UIView實(shí)現(xiàn)示例

    Swift代碼自定義UIView實(shí)現(xiàn)示例

    這篇文章主要為大家介紹了Swift如何自定義UIView的實(shí)現(xiàn)示例代碼,有需要的朋友可以借鑒參考下,希望能夠有所幫助祝大家多多進(jìn)步,早日升職加薪
    2021-10-10
  • 深入探究Swift枚舉關(guān)聯(lián)值的內(nèi)存

    深入探究Swift枚舉關(guān)聯(lián)值的內(nèi)存

    這篇文章主要給大家介紹了關(guān)于Swift枚舉關(guān)聯(lián)值的內(nèi)存的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者使用Swift具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • 舉例講解Swift編程中switch...case語句的用法

    舉例講解Swift編程中switch...case語句的用法

    這篇文章主要介紹了Swift編程中switch...case語句的用法,其中fallthrough關(guān)鍵字在switch語句中的使用是重點(diǎn),需要的朋友可以參考下
    2016-04-04
  • EvenLoop模型在iOS的RunLoop應(yīng)用示例

    EvenLoop模型在iOS的RunLoop應(yīng)用示例

    這篇文章主要為大家介紹了EvenLoop模型在iOS的RunLoop應(yīng)用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • swift中自定義正則表達(dá)式運(yùn)算符=~詳解

    swift中自定義正則表達(dá)式運(yùn)算符=~詳解

    這篇文章主要給大家介紹了關(guān)于swift中自定義正則表達(dá)式運(yùn)算符=~的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-12-12
  • Swift中動態(tài)調(diào)用實(shí)例方法介紹

    Swift中動態(tài)調(diào)用實(shí)例方法介紹

    這篇文章主要介紹了Swift中動態(tài)調(diào)用實(shí)例方法介紹,在Swift中有一類很有意思的寫法,可以讓我們不直接使用實(shí)例來調(diào)用這個實(shí)例上的方法,而是通過類型取出這個類型的某個實(shí)例方法的簽名,然后再通過傳遞實(shí)例來拿到實(shí)際需要調(diào)用的方法,需要的朋友可以參考下
    2015-01-01

最新評論

萍乡市| 石嘴山市| 嘉义市| 清新县| 嵊州市| 铅山县| 绥江县| 康定县| 酒泉市| 石棉县| 仁化县| 明溪县| 米脂县| 萨嘎县| 武城县| 阿荣旗| 剑河县| 宜丰县| 科技| 新平| 芷江| 泸州市| 开鲁县| 沅陵县| 通道| 襄城县| 南郑县| 宿州市| 习水县| 黄大仙区| 炎陵县| 韶山市| 凭祥市| 重庆市| 营山县| 京山县| 永平县| 宁国市| 略阳县| 惠来县| 桦川县|