在Swift中如何实现类型擦除(Type Erasure)

在Swift中,协议(Protocol)可以拥有关联类型(Associated Type),用于实现类似范型(Generic)的功能。但是,带有关联类型的协议并不是真实类型(Existential),所以无法用作独立的类型声明。

比如——

class C<A, B> {}
var c: C<Int, String>

struct S<A, B> {}
var s: S<Int, String>

都是成立的,但——

protocol P {
    associatedtype A
    associatedtype B
}
var p: P<A, B>

是无法成立的。

为此,我们需要使用到类型擦除来将一些带有关联类型的协议转化为带有范型的类(Class)或结构体(Struct)。


考虑有一个协议Callable,它带有两个关联类型InputOutput,并且实现了call(Input) -> Output方法——

protocol Callable {
    associatedtype Input
    associatedtype Output
    
    func call(_ input: Input) -> Output
}

它有一些实现——

struct IncrementByOne : Callable {
    func call(_ input: Int) -> Int {
        return input + 1
    }
}

struct Negation : Callable {
    func call(_ input: Int) -> Int {
        return -input
    }
}

现在,如果我们需要一个变量来存放任意的实现,我们会希望能够书写——

var callable: Callable<Int, Int>
callable = IncrementByOne()
callable.call(0) == 1
callable.call(1) == 2
callable = Negation()
callable.call(0) == 0
callable.call(1) == -1

但是正如之前所说,这样是不可行的。

为此,我们需要一个结构体(或类)AnyCallable,它遵从协议Callable,并且通过范型满足关联类型。我们可以用它转化任意的Callable实例,并与其实现完全相同的功能——

struct AnyCallable<Input, Output> : Callable {
    init<Base : Callable>(_ base: Base) where Base.Input == Input, Base.Output == Output {
        // FIXME: ...
        fatalError()
    }
    
    func call(_ input: Input) -> Output {
        // FIXME: ...
        fatalError()
    }
}

由于其中Base也不是真实类型,所以我们不可能将base保存为AnyCallable的成员。但是,我们可以通过闭包(Closure)将basecall方法保存下来——

struct AnyCallable<Input, Output> : Callable {
    init<Base : Callable>(_ base: Base) where Base.Input == Input, Base.Output == Output {
        _call = base.call
    }
    
    private let _call: (Input) -> Output
    
    func call(_ input: Input) -> Output {
        return _call(input)
    }
}

这样,现在就可以通过——

var callable: AnyCallable<Int, Int>
callable = AnyCallable(IncrementByOne())
callable.call(0) == 1
callable.call(1) == 2
callable = AnyCallable(Negation())
callable.call(0) == 0
callable.call(1) == -1

来实现之前无法实现的逻辑了。


如果一个协议在没有限定为类的情况下拥有可写属性,或者拥有标记为mutating的方法,那它就是可变的。对于这种情况,在获取base的方法作为闭包时,需要先将其转换为变量,即加上var base = base这一行。

考虑如下协议——

protocol ValueHolding {
    associatedtype Value
    
    var value: Value { get set }
}

跟随之前的想法,并考虑通过闭包访问属性,可以很容易写出——

struct AnyValueHolder<Value> : ValueHolding {
    init<Base : ValueHolding>(_ base: Base) where Base.Value == Value {
        var base = base
        _value = (
            get: { return base.value },
            set: { base.value = $0 }
        )
    }
    
    private let _value: (
        get: () -> Value,
        set: (Value) -> ()
    )
    
    var value: Value {
        get { return _value.get() }
        set { _value.set(newValue) }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容