β

Swift 类型擦除

SwiftGG 24 阅读

作者: Mike Ash 原文链接 ,原文日期:2017-12-18
译者: rsenjoyer ;校对: Yousanflics numbbbbb ;定稿: Forelax

你也许曾听过 类型擦除 ,甚至也使用过标准库提供的类型擦除类型如 AnySequence 。但到底什么是类型擦除? 如何自定义类型擦除? 在这篇文章中,我将讨论如何使用类型擦除以及如何自定义。在此感谢 Lorenzo Boaro 提出这个主题。

前言

有时你想对外部调用者隐藏某个类的具体类型,或是一些实现细节。在一些情况下,这样做能防止静态类型在项目中滥用,或者保证了类型间的交互。类型擦除就是移除某个类的具体类型使其变得更通用的过程。

协议或抽象父类可作为类型擦除简单的实现方式之一。例如 NSString 就是一个例子,每次创建一个 NSString 实例时,这个对象并不是一个普通的 NSString 对象,它通常是某个具体的子类的实例,这个子类一般是私有的,同时这些细节通常是被隐藏起来的。你可以使用子类提供的功能而不用知道它具体的类型,你也没必要将你的代码与它们的具体类型联系起来。

在处理 Swift 泛型以及关联类型协议的时候,可能需要使用一些高级的内容。Swift 不允许把协议当做具体的类型来使用。例如,如果你想编写一个方法,它的参数是一个包含了 Int 的序列,那么下面这种做法是不正确的:

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

有时候这样写完全可以,但有些地方还存在一些比较麻烦的情况,通常你不可能只在一个地方添加泛型: 一个泛型函数对其他泛型要求更多… 更糟糕的是,你不能将泛型作为返回值或者属性。这就跟我们想的有点不一样了。

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

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

泛型部分不见了,同时具体的类型也被隐藏起来了。由于使用了 AnySequence 包装具体的值,它带来了一定的代码复杂性以及运行时间成本。但是代码却更简洁了。

Swift 标准库中提供了很多这样的类型,如 AnyCollection AnyHashable AnyIndex 。这些类型在你自定义泛型或协议的时候非常的管用,你也可以直接使用这些类型来简化你的代码。接下来让我们探索实现类型擦除的多种方式吧。

基于类的类型擦除

有时我们需要在不暴露类型信息的情况下从多个类型中包装一些公共的功能,这听起来就像是父类-子类的关系。事实上我们的确可以使用抽象父类来实现类型擦除。父类提供 API 接口,不用去管谁来实现。而子类根据具体的类型信息实现相应的功能。

接下来我们将使用这种方式来自定义 AnySequence ,我们将其命名为 MAnySequence

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

MAnySequence makeIterator 方法实现也差不多。直接调用将抛出异常,这用来提示子类需要重写这个方法:

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

MAnySequenceImpl 需要一个继承于 Iterator 的子类:

var wrapped: Seq.Iterator

init(_ wrapped: Seq.Iterator) {
self.wrapped = wrapped
}

next 方法中调用被包装的序列迭代器:

var seq: Seq

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

从序列中获取迭代器,然后将迭代器包装成 IteratorImpl 对象返回,这样就实现了 makeIterator 的功能。


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

在实际开发中,我们可能会做一些额外的操作来让 MAnySequence 提供一个初始化方法。

我们来试试 MAnySequence

struct MAnySequence<Element>: Sequence {

跟之前一样, MAnySequence 也需要一个可返回的迭代器(Iterator)。迭代器同样被设计为结构体,并持有一个参数为空并返回 Element? 的存储型属性,实际上这个属性是一个函数,被用于 IteratorProtocol 协议的 next 方法中。接下来 Iterator 遵循 IteratorProtocol 协议,并在 next 方法中调用函数:

let _makeIterator: () -> Iterator

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

MAnySequence 的构造函数正是魔法起作用的地方,它接收任意序列作为参数:

_makeIterator = {

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

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

接下来展示如何使用 MAnySequence

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 其他代码可通过调用 count() getElement() 来操作传入的集合。且不会让集合类型破坏 GenericDataSource 泛型参数。

结束语

类型擦除是一种非常有用的技术,它可用来阻止泛型对代码的侵入,也可用来保证接口简单明了。通过将底层类型包装起来,将API与具体的功能进行拆分。这可以通过使用抽象的公共超类和私有子类或将 API 包装在函数中来实现。对于只需要一些功能的简单情况,基于函数类型擦除极其有效。

Swift 标准库提供了几种可直接利用的类型擦除类型。如 AnySequence 包装一个 Sequence ,正如其名, AnySequence 允许你对序列迭代而无需知道序列具体的类型。 AnyIterator 也是类型擦除的类型,它提供一个类型擦除的迭代器。 AnyHashable 也同样是类型擦除的类型,它提供了对Hashable类型访问功能。Swift 还有很多基于集合的擦除类型,你可以通过搜索 Any 来查阅。标准库中也为 Codable API 设计了类型擦除类型: KeyedEncodingContainer KeyedDecodingContainer 。它们都是容器协议类型包装器,可用来在不知道底层具体类型信息的情况下实现 Encode Decode

这就是今天全部的内容了,下次再见。你们的建议对 Friday Q&A 是最好的鼓励,所以如果你关于这个主题有什么好的想法,请 发邮件到这里

本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权,最新文章请访问 http://swift.gg

详细说明 Swift 类型擦除
作者:SwiftGG
走心的 Swift 翻译组
原文地址:Swift 类型擦除, 感谢原作者分享。