类型擦除

近期较忙已经好久没写文章了! 有点懒惰了呢! 今天来跟小伙伴们一起探讨类型擦除,正好也复习一下!

那么什么是类型擦除呢?

moand转换,通过技术手段(通常是包装器),将具体类型的类型信息擦除掉了! 只将类型的抽象信息,通常指的是类型尊从的协议、接口、或基类暴露出来。

假如我们在 AnyGenerator 中定义一个 base 属性用于显式引用实例。当我们将 base 声明为 GenericProtocol,编译器报错:Protocol 'GenericProtocol' can only be used as a generic constraint because it has Self or associated type requirements。

代码如下:

protocol GenericProtocol {
    associatedtype AbstractType

    var value: AbstractType { get set}
    
    func generate(val: AbstractType) -> AbstractType
}

struct AnyGenerator: GenericProtocol {
    var value: T
        
    // error: Protocol 'GenericProtocol' can only be used as a generic constraint because it has Self or associated type requirements
    var _base: GenericProtocol
    
    init(_ base: Base) where Base.AbstractType == T {
        _base = base
    }
    
    func generate(val: T) -> T { val }
}

尝试基于方法指针隐式地引用了 base 实例,如下所示:

struct AnyGenerator: GenericProtocol {
    var value: T
    private let _generate: ((T) -> T)?
    
    init(_ base: Base) where Base.AbstractType == T  {
        self.value = base.value
        _generate = base.generate
    }
    
    func generate(val: T) -> T {
        guard let call = _generate else {
            return T.self as! T
        }
        return call(T.self as! T)
    }
    
}

虽说, 一眼看起来好像是可以了! 但总不能把 base 中的函数或属性都这样写一遍吧!


客官别着急, 这样肯定是不行!

那么我们应该如何基于类型擦除技术解决 Swift 泛型协议的存储问题?

答: 可以通过定义一个类型擦除包装器来瞒过编译器,解决泛型协议 GenericProtocol 的存储问题!

接下来我们就来探讨一下,在泛型协议中,如何显式地引用 base 实例。

中间类型

在上述代码中,在 AnyGenerator 中定义了一个 base 属性,在声明其GenericProtocol 类型时,编译器会报错。为了解决这个问题,可以通过实现一个包装类型作为 base 属性的类型。

首先定义两个类型,两者是基类和子类的关系,并且都遵循泛型协议 GenericProtocol。至于为什么定义两个类型,我们后面再解释。

Base基类代码实现如下,由于泛型类型 GeneratorBoxBase 遵循了 GenericProtocol 泛型协议,类型参数会自动绑定至关联类型。在真正使用时,GeneratorBoxBase 并不会保持抽象,它最终会被绑定到某个特定类型。

class GeneratorBoxBase: GenericProtocol {
    
    var value: T?
    
    func getType(val: T) {
        value = val
    }
    
    func generate(val: T?) -> T? { val }
}

子类代码实现如下,其内部封装了一个实例 var _base: Base,并且将方法传递给了实例。这个 base 实例才是 GenericProtocol 协议真正的实现者。在 GeneratorBox 类型声明中,会将其Base.AbstractType (GenericProtocol 协议的关联类型)绑定为 GeneratorBoxBase中的泛型类型,也就是

此时,我们也就不需要在 GeneratorBox 内部通过 Base.AbstractType == T 的方式进行类型校验。

class GeneratorBox: GeneratorBoxBase {
    
    private(set) var _base: Base?
    
    init(base: Base) {
        _base = base
    }
}
类型擦除

在实现了中间类型后,接下来我们开始使用。首先 struct People 与 class Person各自创建一个,由于我们使用中间类型 GeneratorBox 对 base 进行了封装,可以直接将其对象添加到Box中,进行管理以及使用

具体代码如下所示:

struct People: GenericProtocol {
    var value: T

    mutating func getType(val: T) {
        self.value = val
        print("People : \(val)")
    }
    
    func generate(val: T) -> T {
        val
    }
}

class Person: GenericProtocol {
    var value: T?
    func getType(val: T) {
        value = val
    }
    
    func generate(val: T?) -> T? { val }
}

var people = People(value: 1)
people.getType(val: 18)

let printPeople = GeneratorBox(base: people)
print(printPeople._base?.value)

let person = Person()
person.getType(val: "demo")

let printString = GeneratorBox(base: person)
print(printString._base?.value!)

现在,我们再来看前文留下的问题:为什么中间层需要定义基类和子类两个类型?
我们再来看一下只定义一个 中间 类型 GeneratorBox,代码如下所示

struct GeneratorBox : GenericProtocol {
    var value: Base.AbstractType
      
    var _base: Base
        
    func generate(val: Base.AbstractType) -> Base.AbstractType {
        val
    }
}

struct People: GenericProtocol {
    var value: T
    
    mutating func getType(val: T) {
        self.value = val
        print("People : \(val)")
    }
    
    func generate(val: T) -> T { val }
}

let p = People(value: 1)
let box = GeneratorBox(value: 20, _base: p)

这么一看也没毛病呀! 但在实际开发中,对于属性我们有时也会定义为可选类型,我们再来看看, 如果属性为可选类型会发生什么?

// error: Type 'People' does not conform to protocol 'GenericProtocol'
struct People: GenericProtocol {
    var value: T?
    
    mutating func getType(val: T) {
        self.value = val
        print("People : \(val)")
    }
    
    func generate(val: T) -> T {
        val
    }
}

var number: Int? = 1
let p = People(value: number)
// error: Cannot convert value of type 'Int?' to expected argument type 'People.AbstractType'
let box = GeneratorBoxBase(value: number, _base: p)

果然还是会报错的呀! 编译器给你安排的明明白白的!

所以,当我们遇到不能直接使用泛型协议进行变量定义的时候,我们可以利用一些具体实现的通用泛型类,去包装具体实现了该泛型协议的类。

总而言之,只有深入了解了 Swift 类型擦除后,我们才能领会面向协议编程的精髓以及相关设计理念。

你可能感兴趣的:(类型擦除)