国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

聊聊 Swift 中的幻象類型

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

前言

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

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

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

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

為了能夠盡可能多地重復(fù)使用我們?cè)瓉?lái)的文檔處理代碼,我們繼續(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)出完全無(wú)法理解的東西,我們的文本編輯功能將無(wú)法工作,我們的應(yīng)用程序甚至可能最終崩潰。

我們?cè)诰帉懭魏纹渌囟ǜ袷降拇a時(shí)都會(huì)不斷遇到同樣的問(wèn)題,例如,如果我們想通過(guò)實(shí)現(xiàn)一個(gè)解析器和一個(gè)專門的編輯器來(lái)改善編輯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)于如何解決上述問(wè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)——編譯器根本沒(méi)有足夠的信息可以在編譯時(shí)進(jìn)行這種檢查。

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

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

解決上述問(wèn)題的一個(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)步。

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

引入幻象類型

如果我們能找到一種方法,既能為所有格式重用相同的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í),就像我們上面做的那樣,我們通過(guò)傳遞對(duì)該類型本身的引用來(lái)傳遞我們希望字符串被解碼的編碼——在本例中是UTF8。這真的很有趣。如果我們?cè)偕钊胍稽c(diǎn),就會(huì)發(fā)現(xiàn) Swift 標(biāo)準(zhǔn)庫(kù)將我們上面提到的UTF8類型定義為另一個(gè)類似命名空間的枚舉中的一個(gè)無(wú)大小寫枚舉,稱為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í)例化來(lái)表示值或?qū)ο髸r(shí)。事實(shí)上,由于上述枚舉都沒(méi)有任何公開的情況,它們甚至不能被實(shí)例化!

讓我們看看是否可以用同樣的技術(shù)來(lái)解決我們的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)庫(kù)的Unicode枚舉及其各種編碼的啟發(fā),我們將定義一個(gè)類似的枚舉——DocumentFormat——作為三個(gè)無(wú)大小寫的枚舉的命名空間,每種格式都有一個(gè):

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

請(qǐng)注意,這里不涉及任何協(xié)議——任何類型都可以被用作格式,因?yàn)榫拖馭tring和它的各種編碼一樣,我們將只使用文檔的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來(lái)引用一個(gè)文本文檔是相當(dāng)乏味的,所以讓我們也使用類型別名為每種格式定義速記。這將給我們提供漂亮的、有語(yǔ)義的名字,而不需要任何重復(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)和泛型型約束來(lái)實(shí)現(xiàn)。例如,我們可以用一個(gè)生成NSAttributedString的方法來(lái)擴(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 中可能看起來(lái)有點(diǎn) "格格不入"。然而,雖然 Swift 并沒(méi)有像更多的純函數(shù)式語(yǔ)言(如Haskell)那樣為幻象類型提供一流的支持,但在標(biāo)準(zhǔn)庫(kù)和蘋果平臺(tái)SDK的許多不同地方都可以找到這種模式。

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

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

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

結(jié)論

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

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

責(zé)任編輯:姜華 來(lái)源: Swift社區(qū)
相關(guān)推薦

2022-05-25 09:15:01

Swift 5.6占位符

2022-06-13 09:02:06

Swift類型占位符

2022-07-04 08:54:39

Swift處理器項(xiàng)目

2021-07-07 11:41:38

Swift key paths

2022-04-06 09:10:03

抽象類型普通類型Swift

2021-03-02 21:52:48

Hive數(shù)據(jù)類型

2022-06-17 06:23:23

Oracle壓縮類型

2024-08-12 08:50:17

2022-03-31 09:01:10

Swift類型擦除類型安全性

2021-08-31 07:54:24

SQLDblink查詢

2024-04-26 00:00:00

Rust檢查器代碼

2023-11-09 11:56:28

MySQL死鎖

2022-11-04 09:01:33

SwiftPlottable

2021-11-17 08:11:35

MySQL

2025-11-11 01:45:55

Go代碼goroutine

2021-06-29 09:01:50

Swift閉包語(yǔ)言

2024-05-20 08:58:13

Java引用類型垃圾回收器

2020-11-05 09:54:13

5G

2023-08-29 09:46:12

SQLCTE遞歸

2021-12-11 19:00:54

Java中斷機(jī)制
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

日本乱码高清不卡字幕| 亚洲视频狠狠干| 久久99久久亚洲国产| 嫩模私拍啪啪| 久久精品九九| 日韩欧美视频| 日韩精品一区二区三区中文精品| 三级在线免费看| 久久蜜桃资源一区二区老牛| 97超碰蝌蚪网人人做人人爽| 亚洲美女尤物影院| 欧美日韩国产免费一区二区| 一级毛片aaaaaa免费看| 久久久久99精品一区| 亚洲 日韩 国产第一区| 亚洲香蕉av| 69精品小视频| 欧美视频第一| 亚洲成人精品在线| 国产高清视频在线播放| 亚洲综合色区另类av| 成年人在线看片| 高清视频一区二区| 日韩国产高清一区| 欧美精品国产一区| 国产成人一区二区三区电影| 日本在线视频一区二区三区| 一道本无吗dⅴd在线播放一区 | 久久久久国产| 性色av一区二区三区免费| 精品69视频一区二区三区| 日韩av在线免费观看| 青青青草视频在线| 91精品国产乱| 日本福利专区在线观看| 欧美性猛交xxxxxxxx| 国产大片在线免费观看| 在线观看日韩一区| 国产私拍精品| 欧美色欧美亚洲另类二区| 韩国中文免费在线视频| 色婷婷狠狠综合| 国产三级视频在线看| 色婷婷狠狠综合| 成人福利在线| 在线成人免费观看| av网站在线免费| 精品乱人伦一区二区三区| 亚洲综合伊人久久大杳蕉| 欧美va在线播放| 在线看一级片| 精品视频久久久久久久| 国产精品专区免费| 国产一区二区三区视频免费| 亚洲一区二区三区久久久| 欧美日韩成人精品| 欧美有码在线| 国产在线久久久| 欧美激情第8页| 久久五月天婷婷| 美腿丝袜在线亚洲一区| 成人一级生活片| 国产日韩欧美麻豆| 探花国产精品| 欧美日韩亚洲另类| 24小时免费看片在线观看| 亚洲欧美成人网| 国产一区二区三区| 国产精品一区二区三| 亚洲精品国产日韩| 日本不卡一区二区三区四区| 久久久综合精品| 在线播放evaelfie极品| 欧美一区二区三区在线| 亚洲伦乱视频| 国产精品美女免费看| 可以免费看不卡的av网站| 国产精品网站免费| 婷婷综合五月天| 免费一二一二在线视频| 2019中文字幕全在线观看| 精品1区2区3区4区| 男人天堂新网址| 亚洲午夜激情网站| caoporn视频在线| 欧美专区福利在线| 丝袜诱惑亚洲看片| 亚洲精品自拍网| 欧美一级日韩一级| 色综合视频一区二区三区日韩| 国产日韩av高清| 激情综合网天天干| 91破解版在线看| 精品亚洲夜色av98在线观看| 亚洲专区视频| 伊人天天久久大香线蕉av色| 国产精品久久久久久妇女6080| 久久电影视频| 欧美成人免费va影院高清| 午夜精品免费| 一区二区三区国产免费| 欧美一区二区视频在线观看2022| 国产亚洲观看| 欧美二区三区在线| 国产精品久久久一区麻豆最新章节| 精品自拍一区| 69视频在线播放| 蜜臀av一区二区在线观看| 美女av在线免费观看| 亚洲国产高潮在线观看| 香蕉视频国产精品 | 午夜激情久久| 黄色一级视频在线播放| 欧美日韩一二区| 极品国产人妖chinesets亚洲人妖| 欧洲精品久久| 午夜精品一区二区三区免费视频| av成人在线播放| 久久精品国产99精品国产亚洲性色| 国产色产综合产在线视频| 久久av色综合| 国产高清自拍一区| 亚洲影视在线观看| 伊人久久大香线蕉av超碰| 一区二区精品免费视频| 欧美亚洲高清一区| 欧美猛男同性videos| 久久9精品区-无套内射无码| 亚洲国产天堂久久综合网| 激情久久婷婷| 天天综合入口| 57pao精品| 久久久精品人体av艺术| 亚洲成人短视频| 午夜精品电影在线观看| 欧美日韩五月天| 99久久夜色精品国产亚洲狼| 小泽玛利亚视频在线观看| 中文字幕在线看视频国产欧美在线看完整 | 国产成人精品免费视| 怡红院av亚洲一区二区三区h| 精品国产伦一区二区三区观看方式 | 日韩在线观看一区二区三区| 亚洲区成人777777精品| 精品成人一区二区三区四区| 亚洲在线电影| 国产在线看片| 久久久久久久久久久一区| 欧美午夜片欧美片在线观看| 日韩av自拍| 一级毛片在线视频| 亚洲一区二区在线播放| 岛国av一区二区三区| 四季av一区二区凹凸精品| 中文字幕在线免费播放| 成人国内精品久久久久一区| 欧美日韩免费观看中文| 天天射—综合中文网| 青青九九免费视频在线| www.久久久| 欧美日本国产一区| 日韩国产精品久久久| √天堂8资源中文在线| 好吊色视频988gao在线观看| 国产一区二区三区美女| 国产精品国精产品一二| 欧美人与性禽动交精品| 日韩欧美国产不卡| 麻豆国产精品一区二区三区 | 性8sex亚洲区入口| 国产区在线看| 亚洲精品在线视频观看| 亚洲美女性视频| 97久久超碰国产精品| 亚洲欧洲国产精品一区| 777免费视频| 91久久久久久久| 欧美精品在线观看播放| 日韩av中文字幕一区二区| 成人线上视频| 天天爽人人爽夜夜爽| 国产精品天天狠天天看| 欧美无乱码久久久免费午夜一区| 亚洲综合日韩| 精品视频在线一区二区在线| 日韩中文字幕免费在线| 国产精品久久久久免费a∨| 色美美综合视频| 久久精品国产精品亚洲综合| 日日夜夜亚洲| 中文字幕一区二区三区域| 久久99九九| 日日骚久久av| 一级中文字幕一区二区| 99热精品在线| 成人综合网站| 性色视频在线| 国产激情片在线观看| 日韩av手机在线看| 日韩免费一区二区|