欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Swift?中的幻象類型

 更新時(shí)間:2022年05月26日 16:04:13   作者:Swift社區(qū)  
讓我們來看看一種技術(shù),它可以讓我們利用?Swift?的類型系統(tǒng)在編譯時(shí)執(zhí)行更多種類的數(shù)據(jù)驗(yàn)證消除更多潛在的歧義來源,并幫助我們?cè)谡麄€(gè)代碼庫中保持類型安全,通過使用幻象類型(phantom?types),對(duì)Swift?幻象類型相關(guān)知識(shí)感興趣的朋友一起看看吧

前言

模糊的數(shù)據(jù)可以說是一般應(yīng)用程序中最常見的錯(cuò)誤和問題的來源之一。雖然 Swift 通過其強(qiáng)大的類型系統(tǒng)和完善的編譯器幫助我們避免了許多含糊不清的來源——但只要我們無法在編譯時(shí)保證某個(gè)數(shù)據(jù)總是符合我們的要求,就總是有風(fēng)險(xiǎn),我們最終會(huì)處于含糊不清或不可預(yù)測(cè)的狀態(tài)。

本周,讓我們來看看一種技術(shù),它可以讓我們利用 Swift 的類型系統(tǒng)在編譯時(shí)執(zhí)行更多種類的數(shù)據(jù)驗(yàn)證——消除更多潛在的歧義來源,并幫助我們?cè)谡麄€(gè)代碼庫中保持類型安全——通過使用幻象類型(phantom types)。

定義良好,但仍然含糊不清

舉個(gè)例子,假設(shè)我們正在開發(fā)一個(gè)文本編輯器,雖然它最初只支持純文本文件——隨著時(shí)間的推移,我們還增加了對(duì)編輯HTML文檔的支持,以及PDF預(yù)覽。

為了能夠盡可能多地重復(fù)使用我們?cè)瓉淼奈臋n處理代碼,我們繼續(xù)使用與開始時(shí)相同的Document模型——只是現(xiàn)在它獲得了一個(gè)Format屬性,告訴我們正在處理什么樣的文檔:

struct Document {
    enum Format {
        case text
        case html
        case pdf
    }
    var format: Format
    var data: Data
    var modificationDate: Date
    var author: Author
}

能夠避免代碼重復(fù)當(dāng)然是件好事,而且枚舉是當(dāng)我們?cè)谔幚硪粋€(gè)模型的不同格式或變體時(shí)一般情況下建模 的好方法,但是上述那種設(shè)置實(shí)際上最終會(huì)造成相當(dāng)多的模糊性。

例如,我們可能有一些API,只有在調(diào)用給定格式的文檔時(shí)才有意義——比如這個(gè)打開文本編輯器的函數(shù),它假定任何傳入它的Document都是文本文檔:

func openTextEditor(for document: Document) {
    let text = String(decoding: document.data, as: UTF8.self)
    let editor = TextEditor(text: text)
    ...
}

雖然如果我們不小心將一個(gè)HTML文檔傳遞給上述函數(shù)并不是世界末日(HTML畢竟只是文本),但試圖以這種方式打開一個(gè)PDF,很可能會(huì)導(dǎo)致呈現(xiàn)出完全無法理解的東西,我們的文本編輯功能將無法工作,我們的應(yīng)用程序甚至可能最終崩潰。

我們?cè)诰帉懭魏纹渌囟ǜ袷降拇a時(shí)都會(huì)不斷遇到同樣的問題,例如,如果我們想通過實(shí)現(xiàn)一個(gè)解析器和一個(gè)專門的編輯器來改善編輯HTML文檔的用戶體驗(yàn):

func openHTMLEditor(for document: Document) {
    // 就像我們上面用于文本編輯的函數(shù)一樣,
    // 這個(gè)函數(shù)假設(shè)它總是被傳遞給HTML文檔。
    let parser = HTMLParser()
    let html = parser.parse(document.data)
    let editor = HTMLEditor(html: html)
    ...
}

一個(gè)關(guān)于如何解決上述問題的初步想法可能是編寫一個(gè)包裝函數(shù),切換到所傳遞文檔的格式,然后為每種情況打開正確的編輯器。然而,雖然這對(duì)文本和HTML文檔很有效,但由于PDF文檔在我們的應(yīng)用程序中是不可編輯的——當(dāng)遇到PDF時(shí),我們將被迫拋出一個(gè)錯(cuò)誤,觸發(fā)一個(gè)斷言,或以其他方式失敗

func openEditor(for document: Document) {
    switch document.format {
    case .text:
        openTextEditor(for: document)
    case .html:
        openHTMLEditor(for: document)
    case .pdf:
        assertionFailure("Cannot edit PDF documents")
    }
}

上述情況不是很好,因?yàn)樗笪覀冏鳛殚_發(fā)者始終跟蹤我們?cè)谌魏谓o定的代碼路徑中所處理的文件類型,而我們可能犯的任何錯(cuò)誤只能在運(yùn)行時(shí)被發(fā)現(xiàn)——編譯器根本沒有足夠的信息可以在編譯時(shí)進(jìn)行這種檢查。

因此,盡管我們的 "Document "模型乍一看可能非常優(yōu)雅和完善,但事實(shí)證明,它并不完全是手頭情況的正確解決方案。

看起來我們需要一個(gè)協(xié)議!

解決上述問題的一個(gè)方法是把Document變成一個(gè)協(xié)議,而不是作為一個(gè)具體的類型,把它的所有屬性(除了format)都作為要求:

protocol Document {
    var data: Data { get }
    var modificationDate: Date { get }
    var author: Author { get }
}

有了上述變化,我們現(xiàn)在可以為我們的三種文檔格式中的每一種實(shí)現(xiàn)專門的類型,并讓這些類型都符合我們新的文檔協(xié)議——比如這樣:

struct TextDocument: Document {
    var data: Data
    var modificationDate: Date
    var author: Author
}

上述方法的好處是,它使我們既能實(shí)現(xiàn)可以對(duì)任何Document進(jìn)行操作的通用功能,又能實(shí)現(xiàn)只接受某種具體類型的特定API:

// 這個(gè)函數(shù)可以保存任何文件,
// 所以它接受任何符合我們的新文檔協(xié)議。
func save(_ document: Document) {
    ...
}

// 我們現(xiàn)在只能向我們的函數(shù)傳遞文本文件,
// 即打開一個(gè)文本編輯器。
func openTextEditor(for document: TextDocument) {
    ...
}

我們?cè)谏厦嫠龅幕旧鲜菍⒁郧霸谶\(yùn)行時(shí)進(jìn)行的檢查轉(zhuǎn)為在編譯時(shí)進(jìn)行驗(yàn)證——因?yàn)榫幾g器現(xiàn)在能夠檢查我們是否總是向我們的每個(gè)API傳遞正確格式的文件,這是一個(gè)很大的進(jìn)步。

然而,通過執(zhí)行上述改變,我們也失去了我們最初實(shí)現(xiàn)的優(yōu)點(diǎn)——代碼重用。由于我們現(xiàn)在使用一個(gè)協(xié)議來表示所有的文檔格式,我們將需要為我們的三種文檔類型中的每一種編寫完全重復(fù)的模型實(shí)現(xiàn),以及為我們將來可能增加的任何其他格式提供支持。

引入幻象類型

如果我們能找到一種方法,既能為所有格式重用相同的Document模型,又能在編譯時(shí)驗(yàn)證我們特定格式的代碼,豈不妙哉?事實(shí)證明,我們之前的一行代碼實(shí)際上可以給我們一個(gè)實(shí)現(xiàn)這一目標(biāo)的提示:

let text = String(decoding: document.data, as: UTF8.self)

當(dāng)把Data轉(zhuǎn)換為String時(shí),就像我們上面做的那樣,我們通過傳遞對(duì)該類型本身的引用來傳遞我們希望字符串被解碼的編碼——在本例中是UTF8。這真的很有趣。如果我們?cè)偕钊胍稽c(diǎn),就會(huì)發(fā)現(xiàn) Swift 標(biāo)準(zhǔn)庫將我們上面提到的UTF8類型定義為另一個(gè)類似命名空間的枚舉中的一個(gè)無大小寫枚舉,稱為Unicode。

enum Unicode {
    enum UTF8 {}
    ...
}
typealias UTF8 = Unicode.UTF8

請(qǐng)注意,如果你看一下UTF8類型的實(shí)際實(shí)現(xiàn),它確實(shí)包含一個(gè)私有case,只是為了向后兼容 Swift 3 而存在。

我們?cè)谶@里看到的是一種被稱為幻象類型的技術(shù)——當(dāng)類型被用作標(biāo)記,而不是被實(shí)例化來表示值或?qū)ο髸r(shí)。事實(shí)上,由于上述枚舉都沒有任何公開的情況,它們甚至不能被實(shí)例化!

讓我們看看是否可以用同樣的技術(shù)來解決我們的Document困境。我們首先將Document還原成一個(gè)結(jié)構(gòu)體,只是這次我們將刪除它的format屬性(以及相關(guān)的枚舉),而將它變成一個(gè)覆蓋任何Format類型的泛型——比如這樣:

struct Document<Format> {
    var data: Data
    var modificationDate: Date
    var author: Author
}

受標(biāo)準(zhǔn)庫的Unicode枚舉及其各種編碼的啟發(fā),我們將定義一個(gè)類似的枚舉——DocumentFormat——作為三個(gè)無大小寫的枚舉的命名空間,每種格式都有一個(gè):

enum DocumentFormat {
    enum Text {}
    enum HTML {}
    enum PDF {}
}

請(qǐng)注意,這里不涉及任何協(xié)議——任何類型都可以被用作格式,因?yàn)榫拖?code>String和它的各種編碼一樣,我們將只使用文檔的Format類型作為編譯時(shí)的標(biāo)記。這將使我們能夠像這樣寫出我們特定格式的API:

func openTextEditor(for document: Document<DocumentFormat.Text>) {
    ...
}
func openHTMLEditor(for document: Document<DocumentFormat.HTML>) {
    ...
}
func openPreview(for document: Document<DocumentFormat.PDF>) {
    ...
}

當(dāng)然,我們?nèi)匀豢梢跃帉懖恍枰魏翁囟ǜ袷降耐ㄓ么a。例如,這里我們可以把之前的saveAPI變成一個(gè)完全通用的函數(shù):

func save<F>(_ document: Document<F>) {
    ...
}

然而,總是輸入Document<DocumentFormat.Text>來引用一個(gè)文本文檔是相當(dāng)乏味的,所以讓我們也使用類型別名為每種格式定義速記。這將給我們提供漂亮的、有語義的名字,而不需要任何重復(fù)的代碼:

typealias TextDocument = Document<DocumentFormat.Text>
typealias HTMLDocument = Document<DocumentFormat.HTML>
typealias PDFDocument = Document<DocumentFormat.PDF>

在涉及到特定格式的擴(kuò)展時(shí),幻象類型也確實(shí)大放異彩,現(xiàn)在可以直接使用 Swift 強(qiáng)大的泛型系統(tǒng)和泛型型約束來實(shí)現(xiàn)。例如,我們可以用一個(gè)生成NSAttributedString的方法來擴(kuò)展所有文本文檔:

extension Document where Format == DocumentFormat.Text {
    func makeAttributedString(withFont font: UIFont) -> NSAttributedString {
        let string = String(decoding: data, as: UTF8.self)

        return NSAttributedString(string: string, attributes: [
            .font: font
        ])
    }
}

由于我們的幻象類型在最后只是普通的類型——我們也可以讓它們遵守協(xié)議,并使用這些協(xié)議作為泛型約束。例如,我們可以讓我們的一些DocumentFormat類型遵守Printable協(xié)議,然后我們可以在打印代碼中使用這些協(xié)議作為約束條件。這里有大量的可能性。

一個(gè)標(biāo)準(zhǔn)的模式

起初,幻象類型在 Swift 中可能看起來有點(diǎn) "格格不入"。然而,雖然 Swift 并沒有像更多的純函數(shù)式語言(如Haskell)那樣為幻象類型提供一流的支持,但在標(biāo)準(zhǔn)庫和蘋果平臺(tái)SDK的許多不同地方都可以找到這種模式。

例如,FoundationMeasurement API使用幻象類型來確保在傳遞各種測(cè)量值時(shí)的類型安全——例如度數(shù)、長(zhǎng)度和重量:

let meters = Measurement<UnitLength>(value: 5, unit: .meters)
let degrees = Measurement<UnitAngle>(value: 90, unit: .degrees)

通過使用幻影類型,上述兩個(gè)測(cè)量值不能被混合,因?yàn)槊總€(gè)值是哪種單位,都被編碼到該值的類型中。這可以防止我們不小心將一個(gè)長(zhǎng)度傳遞給一個(gè)接受角度的函數(shù),反之亦然——就像我們之前防止文檔格式被混淆一樣。

結(jié)論

使用幻象類型是一種非常強(qiáng)大的技術(shù),它可以讓我們利用類型系統(tǒng)來驗(yàn)證一個(gè)特定值的不同變體。雖然使用幻象類型通常會(huì)使API更加冗長(zhǎng),而且確實(shí)伴隨著泛型的復(fù)雜性——當(dāng)處理不同的格式和變體時(shí),它可以讓我們減少對(duì)運(yùn)行時(shí)檢查的依賴,而讓編譯器來執(zhí)行這些檢查。

就像一般的泛型一樣,我認(rèn)為在部署幻象類型之前,首先要仔細(xì)評(píng)估當(dāng)前的情況,這很重要。就像我們最初的Document模型并不是手頭任務(wù)的正確選擇,盡管它的結(jié)構(gòu)很好,但如果部署在錯(cuò)誤的情況下,幻象類型會(huì)使簡(jiǎn)單的設(shè)置變得更加復(fù)雜。像往常一樣,它歸結(jié)為為工作選擇正確的工具。

到此這篇關(guān)于Swift 中的幻象類型的文章就介紹到這了,更多相關(guān)Swift 幻象類型內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 用Swift構(gòu)建一個(gè)簡(jiǎn)單的iOS郵件應(yīng)用的方法

    用Swift構(gòu)建一個(gè)簡(jiǎn)單的iOS郵件應(yīng)用的方法

    這篇文章主要介紹了用Swift構(gòu)建一個(gè)簡(jiǎn)單的iOS郵件應(yīng)用的方法,包括查看和標(biāo)記已讀等基本的郵件應(yīng)用功能,需要的朋友可以參考下
    2015-07-07
  • Swift 常量與變量實(shí)例詳解

    Swift 常量與變量實(shí)例詳解

    這篇文章主要介紹了Swift 常量與變量實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下
    2017-06-06
  • swift中defer幾個(gè)簡(jiǎn)單的使用場(chǎng)景詳解

    swift中defer幾個(gè)簡(jiǎn)單的使用場(chǎng)景詳解

    在Swift 2.0中,Apple提供了defer關(guān)鍵字,讓我們可以實(shí)現(xiàn)同樣的效果,這篇文章主要介紹了關(guān)于swift中defer幾個(gè)簡(jiǎn)單的使用場(chǎng)景的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用defer具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧。
    2018-03-03
  • Swift高階函數(shù)contains?allSatisfy?reversed?lexicographicallyPrecedes用法示例

    Swift高階函數(shù)contains?allSatisfy?reversed?lexicographicallyPr

    這篇文章主要為大家介紹了Swift高階函數(shù)contains?allSatisfy?reversed?lexicographicallyPrecedes用法示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • Swift中static和class關(guān)鍵字的深入講解

    Swift中static和class關(guān)鍵字的深入講解

    這篇文章主要給大家介紹了關(guān)于Swift中static和class關(guān)鍵字的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • Swift的74個(gè)常用內(nèi)置函數(shù)介紹

    Swift的74個(gè)常用內(nèi)置函數(shù)介紹

    這篇文章主要介紹了Swift的74個(gè)常用內(nèi)置函數(shù)介紹,這篇文章列舉出了所有的Swift庫函數(shù),內(nèi)置函數(shù)是指無需引入任何模塊即可以直接使用的函數(shù),需要的朋友可以參考下
    2015-01-01
  • Swift實(shí)現(xiàn)倒計(jì)時(shí)5秒功能

    Swift實(shí)現(xiàn)倒計(jì)時(shí)5秒功能

    這篇文章主要為大家詳細(xì)介紹了Swift實(shí)現(xiàn)倒計(jì)時(shí)5秒功能,在“登錄”和“注冊(cè)”頁面也有相似功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-03-03
  • NotificationCenter類實(shí)現(xiàn)原理

    NotificationCenter類實(shí)現(xiàn)原理

    這篇文章主要為大家介紹了NotificationCenter類實(shí)現(xiàn)原理源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • Swift中的可變參數(shù)函數(shù)介紹

    Swift中的可變參數(shù)函數(shù)介紹

    這篇文章主要介紹了Swift中的可變參數(shù)函數(shù)介紹,本文實(shí)現(xiàn)了和Objective-C調(diào)用方法一樣的變參數(shù)函數(shù),需要的朋友可以參考下
    2015-01-01
  • Swift實(shí)現(xiàn)無限輪播效果

    Swift實(shí)現(xiàn)無限輪播效果

    這篇文章主要為大家詳細(xì)介紹了Swift無限輪播效果實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-03-03

最新評(píng)論