Swift 型別擦除

2021-09-11 10:02:44 字數 3932 閱讀 5360

你也許曾聽過型別擦除,甚至也使用過標準庫提供的型別擦除型別如anysequence。但到底什麼是型別擦除? 如何自定義型別擦除? 在這篇文章中,我將討論如何使用型別擦除以及如何自定義。在此感謝 lorenzo boaro 提出這個主題。

有時你想對外部呼叫者隱藏某個類的具體型別,或是一些實現細節。在一些情況下,這樣做能防止靜態型別在專案中濫用,或者保證了型別間的互動。型別擦除就是移除某個類的具體型別使其變得更通用的過程。

協議或抽象父類可作為型別擦除簡單的實現方式之一。例如nsstring就是乙個例子,每次建立乙個nsstring例項時,這個物件並不是乙個普通的nsstring物件,它通常是某個具體的子類的例項,這個子類一般是私有的,同時這些細節通常是被隱藏起來的。你可以使用子類提供的功能而不用知道它具體的型別,你也沒必要將你的**與它們的具體型別聯絡起來。

在處理 swift 泛型以及關聯型別協議的時候,可能需要使用一些高階的內容。swift 不允許把協議當做具體的型別來使用。例如, 如果你想編寫乙個方法,他的引數是乙個包含了int的序列,那麼下面這種做法是不正確的:

func f(seq: sequence) 

}複製**

manysequencemakeiterator方法實現也差不多。直接呼叫將丟擲異常,這用來提示子類需要重寫這個方法:

func makeiterator() -> iterator 

}複製**

這樣就定義了乙個基於類的型別擦除的api,私有的子類將來實現這些api。公共類通過元素型別引數化,但私有實現類由它包裝的序列型別進行引數化:

private class manysequenceimpl: manysequence

複製**

在next方法中呼叫被包裝的序列迭代器:

override func next() -> seq.element?

}複製**

相似地,manysequenceimpl包裝乙個序列:

var seq: seq

init(_ seq: seq)

複製**

從序列中獲取迭代器,然後將迭代器包裝成iteratorimpl物件返回,這樣就實現了makeiterator的功能。

override func makeiterator() -> iteratorimpl 

}複製**

我們需要一種方法來實際建立這些東西: 對manysequence新增乙個靜態方法,該方法建立乙個manysequenceimpl例項,並將其作為manysequence型別返回給呼叫者。

extension manysequence 

}複製**

在實際開發中,我們可能會做一些額外的操作來讓manysequence提供乙個初始化方法。

我們來試試manysequence

func printints(_ seq: manysequence) 

}let array = [1, 2, 3, 4, 5]

printints(manysequence.make(array))

printints(manysequence.make(array[1 ..< 4]))

複製**

完美!

有時我們希望對外暴露支援多種型別的方法,但又不想指定具體的型別。乙個簡單的辦法就是,儲存那些簽名僅涉及到我們想公開的型別的函式,函式主體在底層已知具體實現型別的上下文中建立。

我們一起看看如何運用這種方法來設計manysequence,與前面的實現很類似。它是乙個結構體而非類,這是因為它僅僅作為容器使用,不需要有任何的繼承關係。

struct manysequence: sequence 

}複製**

manysequenceiterator很相似: 持有乙個引數為空返回iterator型別的儲存型屬性。遵循sequence協議並在makeiterator方法中呼叫這個屬性。

let _makeiterator: () -> iterator

func makeiterator() -> iterator

複製**

manysequence的建構函式正是魔法起作用的地方,它接收任意序列作為引數:

init(_ seq: seq) where seq.element == element )}}

}複製**

接下來展示如何使用manysequence

func printints(_ seq: manysequence) 

}let array = [1, 2, 3, 4, 5]

printints(manysequence(array))

printints(manysequence(array[1 ..< 4]))

複製**

正確執行,太棒了!

當需要將小部分功能包裝為更大型別的一部分時,這種基於函式的型別擦除方法特別實用,這樣做就不需要有單獨的類來實現被擦除型別的這部分功能了。

比方說你現在想要編寫一些適用於各種集合型別的**,但它真正需要能夠對這些集合執行的操作是獲取計數並執行從零開始的整數下標。如訪問tableview資料來源。它可能看起來像這樣:

class genericdatasource

getelement = }}

複製**

genericdatasource其他**可通過呼叫count()getelement()來操作傳入的集合。且不會讓集合型別破壞genericdatasource泛型引數。

型別擦除是一種非常有用的技術,它可用來阻止泛型對**的侵入,也可用來保證介面簡單明瞭。通過將底層型別包裝起來,將api與具體的功能進行拆分。這可以通過使用抽象的公共超類和私有子類或將 api 包裝在函式中來實現。對於只需要一些功能的簡單情況,基於函式型別擦除極其有效。

swift 標準庫提供了幾種可直接利用的型別擦除型別。如anysequence包裝乙個sequence,正如其名,anysequence允許你對序列迭代而無需知道序列具體的型別。anyiterator也是型別擦除的型別,它提供乙個型別擦除的迭代器。anyhashable也同樣是型別擦除的型別,它提供了對hashable型別訪問功能。swift 還有很多基於集合的擦除型別,你可以通過搜尋any來查閱。標準庫中也為codableapi 設計了型別擦除型別:keyedencodingcontainerkeyeddecodingcontainer。它們都是容器協議型別包裝器,可用來在不知道底層具體型別資訊的情況下實現encodedecode

這就是今天全部的內容了,下次再見。你們的建議對 friday q&a 是最好的鼓勵,所以如果你關於這個主題有什麼好的想法,請 發郵件到這裡。

scala 模式匹配中的型別擦除

在預設情況下,基於jvm的scala在執行時是沒有型別資訊,在容器型別中,只有array在執行是保留了型別資訊,其它容器不保留型別資訊,測試 如下 import scala.reflect.object patterntest def isintlist x any x match def isst...

Swift 型別轉換

import uikit 1.定義基類 mediaitem 2.定義子類 movie 3.定義子類 song class mediaitem class movie mediaitem class song mediaitem let library movie name movie1 direct...

Swift 型別約束

型別約束 指必須繼承指定的類或者遵循特定的協議 語法 funcsomefunc somet t,someu u 表示函式有兩個引數 somet 和someu 型別分別是t和 u,其中t是 someclass 子類,u 遵循someprotocol 先看非泛型的函式 func findstringin...