Swift 5.6 SE-0335 Introduce existential any的理解

在Swift 5.6的更新list里面看到了existential any的简单介绍,但是由于过于简单导致一头雾水,经过一些查证,这里说一下对这个改动的理解。SE-0335

举例简述

通常我们在使用协议时,是为了保证类型的一致性并遵守一些要求,例如实现某个方法:

protocol ProtocolA {
    func test()
}

struct Test: ProtocolA {
    func test() {
        print("hello")
    }
}

let t = Test()
t.test() /// Static Dispatch
复制代码

此时的t是明确类型的,Swift会对t.test()的调用使用Static Dispatch,效率更高。

如果我们因为某些情况下无法明确类型(确实会出现这种情况),还想调用test方法,那么我们会出现这种写法:

let t: ProtocolA = Test()
t.test() /// Dynamic Dispatch
复制代码

此时的t的类型我们就可以称作existential typeSwift会使用Dynamic Dispatch完成test调用,当前场景是V-Table调用,这种效率要低于静态派发。

所以在SE-0335中新增了any关键字来处理existential type的情况,上面的写法在5.6中应该为:

let t: any ProtocolA = Test()
t.test() /// Static Dispatch
复制代码

此时就会走静态派发了。

进一步举例

可能你觉得这种场景很少见,那么我再举两个例子

例子1

struct A {
    var obs: [ProtocolA]?
}

let a = A()
a.obs?.map {
    /// dynamic dispatch
    $0.test()
}

struct B {
    /// 引入any关键字
    var obs: [any ProtocolA]?
}

let b = B()
b.obs?.map {
    /// static dispatch
    $0.test()
}
复制代码

可以看到,在日常开发中,上面这个场景经常会遇到,原有的写法会对每个ob进行动态派发,而引入了any关键字后,变为了静态派发,提高了效率。

例子2

第二个场景是本人在写基础组件时遇到过的疑惑,可以先看下代码:

func test1(obj: ProtocolA) {
    obj.test()
}

func test2<T: ProtocolA>(obj: T) {
    obj.test() 
}
复制代码

之前很疑惑这两种写法的区别,因为编译层面看没什么差异(当方法使用泛型作为返回值时可以明确类型)。现在,通过上面的分析,我们可以理解了,test1中方法调用是动态派发,而test2的方法调用是静态派发,显然后者是更为科学的写法,然后我们在使用的时候前者却更容易理解,所以any关键字就派上用场了:

func test(obj: any ProtocolA) {
    obj.test() /// Static Dispatch
}
复制代码

以上。

后话

目前Xcode 13.3可以使用此特性。对于此特性,按照苹果的计划,在未来的Swift 5.x版本中,将对涉及到的地方进行警告,而在Swift 6开始会直接抛出错误。

这种变化有可能在未来破坏现有代码,所以在过渡期应该尽可能地去使用正确的形式。在个人看来这个改动非常重要,通过理解的过程也可以加深对Protocol类型的理解。

猜你喜欢

转载自juejin.im/post/7078192732635676680
5.6