使用泛型和协议作为类型参数之间的区别,在函数中实现它们的优缺点是什么

2024-02-25

由于 Swift 允许我们在函数中使用 Protocol 和 Generic 作为参数类型,所以我想到了下面的场景:

protocol AProtocol {
    var name: String{ get }
}

class ClassA: AProtocol {
    var name = "Allen"
}

func printNameGeneric<T: AProtocol>(param: T) {
    print(param.name)
}

func printNameProtocol(param: AProtocol) {
    print(param.name)
}

第一个函数使用泛型作为带有类型约束的参数类型,第二个函数直接使用协议作为参数类型。然而,这两个函数可以具有相同的效果,这是让我困惑的一点。所以我的问题是:

  1. 他们每个人的具体场景是什么(或者只能由特定的一个人完成,而另一个人则不能完成的情况)?

  2. 对于给定的情况,两个函数产生相同的结果。哪一种更好实施(或每种方案的优缺点)?

这场精彩的演讲 https://developer.apple.com/videos/play/wwdc2016/416/已经提到过通用专业化,这是一种将函数调度方式从动态调度(函数与非通用参数) to 静态调度 or inlining(函数与通用参数). Since 静态调度 and inlining相比之下更便宜动态调度,用泛型实现函数总能提供更好的性能。

@Hamish 还提供了很好的信息这个帖子 https://stackoverflow.com/questions/38446487/what-is-the-in-practice-difference-between-generic-and-protocol-typed-function-p,查看更多信息。

这是我遇到的一个新问题:

struct StructA: AProtocol {
    var a: Int
}

struct StructB: AProtocol {
    var b: Int
}

func buttonClicked(sender: UIButton) {
    var aVar: AProtocol

    if sender == self.buttonA
    {
        aVar = StructA(a: 1)
    }
    else if sender == self.buttonA
    {
        aVar = StructB(b: 2)
    }

    foo(param: aVar)
}

func foo<T: AProtocol>(param: T) {
    //do something
}
  1. 如果有多种类型符合协议,并在不同条件下动态地传递给通用函数。如上所示,按不同的按钮会将不同类型(StructA 或 StructB)的参数传递给函数,在这种情况下通用专业化仍然有效吗?

实际上,今年的 WWDC 上有一个关于此内容的视频(它是关于类、结构和协议的性能;我没有链接,但您应该能够找到它)。

在第二个函数中,您传递符合该协议的任何值,实际上是在传递一个容器,该容器为传递的值提供 24 字节的存储空间,并为类型相关信息提供 16 字节的存储空间(为了确定要调用哪些方法,因此,动态调度)。如果现在传递的值大于内存中的 24 个字节,则该对象将在堆上分配,并且容器存储对该对象的引用!这实际上非常耗时,如果可能的话当然应该避免。

在您使用通用约束的第一个函数中,编译器实际上创建了另一个函数,该函数显式对该类型执行函数的操作。 (如果您将此函数与许多不同的类型一起使用,那么您的代码大小可能会显着增加;请参阅 C++ 代码膨胀以获取更多参考。)但是,编译器现在可以静态分派方法,如果可能的话内联函数,并且确实如此不必分配任何堆空间。上面提到的视频中提到,代码大小不必显着增加,因为代码仍然可以共享......因此具有通用约束的函数当然是可行的方法!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用泛型和协议作为类型参数之间的区别,在函数中实现它们的优缺点是什么 的相关文章

随机推荐