返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >详解Swift 中的幻象类型
  • 715
分享到

详解Swift 中的幻象类型

2024-04-02 19:04:59 715人浏览 泡泡鱼
摘要

目录前言定义良好,但仍然含糊不清看起来我们需要一个协议!引入幻象类型一个标准的模式结论前言 模糊的数据可以说是一般应用程序中最常见的错误和问题的来源之一。虽然 Swift 通过其强大

前言

模糊的数据可以说是一般应用程序中最常见的错误和问题的来源之一。虽然 Swift 通过其强大的类型系统和完善的编译器帮助我们避免了许多含糊不清的来源——但只要我们无法在编译时保证某个数据总是符合我们的要求,就总是有风险,我们最终会处于含糊不清或不可预测的状态。

本周,让我们来看看一种技术,它可以让我们利用 Swift 的类型系统在编译时执行更多种类的数据验证——消除更多潜在的歧义来源,并帮助我们在整个代码库中保持类型安全——通过使用幻象类型(phantom types)。

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

举个例子,假设我们正在开发一个文本编辑器,虽然它最初只支持纯文本文件——随着时间的推移,我们还增加了对编辑html文档的支持,以及pdf预览。

为了能够尽可能多地重复使用我们原来的文档处理代码,我们继续使用与开始时相同的Document模型——只是现在它获得了一个FORMat属性,告诉我们正在处理什么样的文档:

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

能够避免代码重复当然是件好事,而且枚举是当我们在处理一个模型的不同格式或变体时一般情况下建模 的好方法,但是上述那种设置实际上最终会造成相当多的模糊性。

例如,我们可能有一些api,只有在调用给定格式的文档时才有意义——比如这个打开文本编辑器的函数,它假定任何传入它的Document都是文本文档:

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

虽然如果我们不小心将一个HTML文档传递给上述函数并不是世界末日(HTML毕竟只是文本),但试图以这种方式打开一个PDF,很可能会导致呈现出完全无法理解的东西,我们的文本编辑功能将无法工作,我们的应用程序甚至可能最终崩溃。

我们在编写任何其他特定格式的代码时都会不断遇到同样的问题,例如,如果我们想通过实现一个解析器和一个专门的编辑器来改善编辑HTML文档的用户体验:

func openHTMLEditor(for document: Document) {
    // 就像我们上面用于文本编辑的函数一样,
    // 这个函数假设它总是被传递给HTML文档。
    let parser = HTMLParser()
    let html = parser.parse(document.data)
    let editor = HTMLEditor(html: html)
    ...
}

一个关于如何解决上述问题的初步想法可能是编写一个包装函数,切换到所传递文档的格式,然后为每种情况打开正确的编辑器。然而,虽然这对文本和HTML文档很有效,但由于PDF文档在我们的应用程序中是不可编辑的——当遇到PDF时,我们将被迫抛出一个错误,触发一个断言,或以其他方式失败:

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")
    }
}

上述情况不是很好,因为它要求我们作为开发者始终跟踪我们在任何给定的代码路径中所处理的文件类型,而我们可能犯的任何错误只能在运行时被发现——编译器根本没有足够的信息可以在编译时进行这种检查。

因此,尽管我们的 "Document "模型乍一看可能非常优雅和完善,但事实证明,它并不完全是手头情况的正确解决方案。

看起来我们需要一个协议!

解决上述问题的一个方法是把Document变成一个协议,而不是作为一个具体的类型,把它的所有属性(除了format)都作为要求:

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

有了上述变化,我们现在可以为我们的三种文档格式中的每一种实现专门的类型,并让这些类型都符合我们新的文档协议——比如这样:

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

上述方法的好处是,它使我们既能实现可以对任何Document进行操作的通用功能,又能实现只接受某种具体类型的特定API:

// 这个函数可以保存任何文件,
// 所以它接受任何符合我们的新文档协议。
func save(_ document: Document) {
    ...
}

// 我们现在只能向我们的函数传递文本文件,
// 即打开一个文本编辑器。
func openTextEditor(for document: TextDocument) {
    ...
}

我们在上面所做的基本上是将以前在运行时进行的检查转为在编译时进行验证——因为编译器现在能够检查我们是否总是向我们的每个API传递正确格式的文件,这是一个很大的进步。

然而,通过执行上述改变,我们也失去了我们最初实现的优点——代码重用。由于我们现在使用一个协议来表示所有的文档格式,我们将需要为我们的三种文档类型中的每一种编写完全重复的模型实现,以及为我们将来可能增加的任何其他格式提供支持。

引入幻象类型

如果我们能找到一种方法,既能为所有格式重用相同的Document模型,又能在编译时验证我们特定格式的代码,岂不妙哉?事实证明,我们之前的一行代码实际上可以给我们一个实现这一目标的提示:

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

当把Data转换为String时,就像我们上面做的那样,我们通过传递对该类型本身的引用来传递我们希望字符串被解码的编码——在本例中是UTF8。这真的很有趣。如果我们再深入一点,就会发现 Swift 标准库将我们上面提到的UTF8类型定义为另一个类似命名空间的枚举中的一个无大小写枚举,称为Unicode

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

请注意,如果你看一下UTF8类型的实际实现,它确实包含一个私有case,只是为了向后兼容 Swift 3 而存在。

我们在这里看到的是一种被称为幻象类型的技术——当类型被用作标记,而不是被实例化来表示值或对象时。事实上,由于上述枚举都没有任何公开的情况,它们甚至不能被实例化!

让我们看看是否可以用同样的技术来解决我们的Document困境。我们首先将Document还原成一个结构体,只是这次我们将删除它的format属性(以及相关的枚举),而将它变成一个覆盖任何Format类型的泛型——比如这样:

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

受标准库的Unicode枚举及其各种编码的启发,我们将定义一个类似的枚举——DocumentFormat——作为三个无大小写的枚举的命名空间,每种格式都有一个:

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

请注意,这里不涉及任何协议——任何类型都可以被用作格式,因为就像String和它的各种编码一样,我们将只使用文档的Format类型作为编译时的标记。这将使我们能够像这样写出我们特定格式的API:

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

当然,我们仍然可以编写不需要任何特定格式的通用代码。例如,这里我们可以把之前的saveAPI变成一个完全通用的函数:

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

然而,总是输入Document<DocumentFormat.Text>来引用一个文本文档是相当乏味的,所以让我们也使用类型别名为每种格式定义速记。这将给我们提供漂亮的、有语义的名字,而不需要任何重复的代码:

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

在涉及到特定格式的扩展时,幻象类型也确实大放异彩,现在可以直接使用 Swift 强大的泛型系统和泛型型约束来实现。例如,我们可以用一个生成NSAttributedString的方法来扩展所有文本文档:

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
        ])
    }
}

由于我们的幻象类型在最后只是普通的类型——我们也可以让它们遵守协议,并使用这些协议作为泛型约束。例如,我们可以让我们的一些DocumentFormat类型遵守Printable协议,然后我们可以在打印代码中使用这些协议作为约束条件。这里有大量的可能性。

一个标准的模式

起初,幻象类型在 Swift 中可能看起来有点 "格格不入"。然而,虽然 Swift 并没有像更多的纯函数式语言(如Haskell)那样为幻象类型提供一流的支持,但在标准库和苹果平台SDK的许多不同地方都可以找到这种模式。

例如,FoundationMeasurement API使用幻象类型来确保在传递各种测量值时的类型安全——例如度数、长度和重量:

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

通过使用幻影类型,上述两个测量值不能被混合,因为每个值是哪种单位,都被编码到该值的类型中。这可以防止我们不小心将一个长度传递给一个接受角度的函数,反之亦然——就像我们之前防止文档格式被混淆一样。

结论

使用幻象类型是一种非常强大的技术,它可以让我们利用类型系统来验证一个特定值的不同变体。虽然使用幻象类型通常会使API更加冗长,而且确实伴随着泛型的复杂性——当处理不同的格式和变体时,它可以让我们减少对运行时检查的依赖,而让编译器来执行这些检查。

就像一般的泛型一样,我认为在部署幻象类型之前,首先要仔细评估当前的情况,这很重要。就像我们最初的Document模型并不是手头任务的正确选择,尽管它的结构很好,但如果部署在错误的情况下,幻象类型会使简单的设置变得更加复杂。像往常一样,它归结为为工作选择正确的工具

到此这篇关于Swift 中的幻象类型的文章就介绍到这了,更多相关Swift 幻象类型内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 详解Swift 中的幻象类型

本文链接: https://lsjlt.com/news/149852.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 详解Swift 中的幻象类型
    目录前言定义良好,但仍然含糊不清看起来我们需要一个协议!引入幻象类型一个标准的模式结论前言 模糊的数据可以说是一般应用程序中最常见的错误和问题的来源之一。虽然 Swift 通过其强大...
    99+
    2024-04-02
  • 详细讲解Swift中的类型占位符
    Swift 的类型推断能力从一开始就是语言的核心部分,它极大地减少了我们在声明有默认值的变量和属性时手动指定类型的工作。例如,表达式var number = 7不需要包含任何类型注释...
    99+
    2024-04-02
  • Swift中风味各异的类型擦除实例详解
    目录前言什么时候需要类型擦除通用包装器类型擦除闭包类型擦除结语前言 Swift的总体目标是既强大到可以用于底层系统编程,又足够容易让初学者学习,这有时会导致相当有趣的情况&mdash...
    99+
    2024-04-02
  • Swift 基本数据类型详解总结
    Swift 基本数据类型 有以下几种基本数据类型: 整型 浮点型 布尔型 元组 可选值 Optional 1. 整型 var intT...
    99+
    2024-04-02
  • Swift图表使用Foudation库中测量类型详解
    目录前言定义图表的数据在图表中使用测量值设计一个包装器类型显示格式化标签前言 在这篇文章中,我们将建立一个条形图,比较基督城地区自然散步的持续时间。我们将使用今年推出的新的Swift...
    99+
    2022-11-13
    Swift 图表Foudation库测量类型 Swift Foudation
  • Java对象类型的判断详解
    在Java中,可以使用`instanceof`运算符来判断一个对象的类型。`instanceof`运算符用于检查一个对象是否是一个特...
    99+
    2023-08-15
    Java
  • Swift 字符串类型及常用方法详解总结
    目录1. 构造2. 拼接3. 字符4. 转义符5. 常用方法Swift 字符串类型及常用方法 1. 构造 // 直接赋值 text = "" // 1. 构造方法 text =...
    99+
    2024-04-02
  • Swift中的类型占位符怎么使用
    本篇内容介绍了“Swift中的类型占位符怎么使用”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Swift 的类型推断能力从一开始就是语言的核...
    99+
    2023-07-02
  • Javascript Object对象类型使用详解
    目录1. 生成方法2. 操作属性2.1 读取2.2 赋值2.3 删除2.4 遍历2.5 判断属性是否存在3. with 语句总结1. 生成方法 对象是javaScript中的一种数据...
    99+
    2022-11-13
    JavaScript Object对象 JavaScript Object JS Object
  • Linux 上的 ASP 对象数据类型详解。
    Linux 上的 ASP 对象数据类型详解 ASP 是一种常用的 Web 应用程序开发语言,是一种基于服务器的脚本语言。在 Linux 环境下,ASP 也是可以运行的。在 ASP 中,对象数据类型是一种非常重要的数据类型,本文将对 Linu...
    99+
    2023-08-26
    对象 linux 数据类型
  • 详解Android中的Context抽象类
    关于Context我们首先应该知道: (1)它描述的是一个应用程序环境的信息,即上下文。 (2)该类是一个抽象(abstract class)类,Android提供了该抽象类的...
    99+
    2022-06-06
    context Android
  • Swift类和对象的底层探索分析
    目录引言1. 对象1.1 上层代码中查找1.1.1 查找对象调用方法1.1.2 设置符号断点1.2 swift_allocObject1.3 swift_showAlloc1.4 查...
    99+
    2024-04-02
  • Python中的类对象示例详解
    抽象特点 Python 一切皆对象,基于此概念,对 类 class 有以下特点: 类与实例的属性 类对象创建可选择定义类属性,创建实例对象时,实例属性自动执行类的__init__方...
    99+
    2024-04-02
  • Java中值类型和引用类型详解
    我们都知道java是一种面向对象的编程语言,但是在实际意义上java并不是纯面向对象,因为面向对象的意义就是万物皆对象,那么如果说int类型的变量也是一个对象的话,那么我们应该能用&...
    99+
    2024-04-02
  • 详解TypeScript中的类型保护
    目录概述类型断言in语法instanceof 语法typeof 语法总结概述 在 TypeScript 中使用联合类型时,往往会碰到这种尴尬的情况: interface Bird...
    99+
    2024-04-02
  • 详解Python中的枚举类型
    目录什么是枚举类型为什么要使用枚举如何使用枚举从字典创建枚举最后的话你好,我是 征哥,今天分享一下 Python 中的枚举类型,为什么需要枚举类型,及如何使用。 什么是枚举类型 枚举...
    99+
    2024-04-02
  • python中的类型和对象
    type 类继承object类,由type自己实例化而来object由type类实例化而来,object没有基类list类有type类实例化来,继承自object类mylist由list类实例化而来,不继承任何类type(list)查看li...
    99+
    2023-01-31
    对象 类型 python
  • OpenStack的Swift组件详解
    目录一:简介背景原理特性二:架构核心架构组件详解Swift对CAP的支持程度三:常用操作一:简介 背景 1.Swift 最初是由 Rackspace 公司开发的高可用分布式对象存储服...
    99+
    2024-04-02
  • 详解MySQL中数据类型和字段类型
    目录1. mysql的数据类型 (1)数值型 (2)字符(串)型 (3)日期和时间型 (4)null值 2. mysql的列(字段)类型 2.1数值列类型&nb...
    99+
    2024-04-02
  • Python NumPy教程之数据类型对象详解
    每个 ndarray 都有一个关联的数据类型 (dtype) 对象。这个数据类型对象(dtype)告诉我们数组的布局。这意味着它为我们提供了以下信息: 数据类型(整数、浮点数、Pyt...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作