美文网首页
类型擦除

类型擦除

作者: 大王叫我来巡山丨 | 来源:发表于2022-01-08 10:29 被阅读0次

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

    那么什么是类型擦除呢?

    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<T>: 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: GenericProtocol>(_ base: Base) where Base.AbstractType == T {
            _base = base
        }
        
        func generate(val: T) -> T { val }
    }
    

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

    struct AnyGenerator<T>: GenericProtocol {
        var value: T
        private let _generate: ((T) -> T)?
        
        init<Base: GenericProtocol>(_ 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<T>: 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中的泛型类型,也就是 <T>。

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

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

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

    具体代码如下所示:

    struct People<T>: GenericProtocol {
        var value: T
    
        mutating func getType(val: T) {
            self.value = val
            print("People : \(val)")
        }
        
        func generate(val: T) -> T {
            val
        }
    }
    
    class Person<T>: 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<String>()
    person.getType(val: "demo")
    
    let printString = GeneratorBox(base: person)
    print(printString._base?.value!)
    

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

    struct GeneratorBox <Base: GenericProtocol>: GenericProtocol {
        var value: Base.AbstractType
          
        var _base: Base
            
        func generate(val: Base.AbstractType) -> Base.AbstractType {
            val
        }
    }
    
    struct People<T>: 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<T>' does not conform to protocol 'GenericProtocol'
    struct People<T>: 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<Int>.AbstractType'
    let box = GeneratorBoxBase(value: number, _base: p)
    

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

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

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

    相关文章

      网友评论

          本文标题:类型擦除

          本文链接:https://www.haomeiwen.com/subject/odukcrtx.html