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

Uto*_*olk 15 generics parameters protocols swift

由于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)
}
Run Code Online (Sandbox Code Playgroud)

第一个函数使用generic作为带有类型约束的参数类型,第二个函数直接使用protocol作为参数类型.但是,这两个函数可以产生相同的效果,这一点让我感到困惑.所以我的问题是:

  1. 每种情况的具体情况是什么(或者只能通过特定情况而不是另一种情况进行的情况)?

  2. 对于给定的情况,两个函数都得出相同的结果.哪一个更好实现(或者每个的优点和缺点)?

这个伟大的演讲提到了泛型专业化,这是一种优化,它将函数调度的方式从动态调度(具有非泛型参数的函数)转变为静态调度内联(具有泛型参数的函数).由于静态调度内联动态调度相比成本较低,因此使用泛型实现函数总能提供更好的性能.

@Hamish在这篇文章中也提供了很多信息,请查看更多信息.

这是一个新的问题来找我:

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
}
Run Code Online (Sandbox Code Playgroud)
  1. 如果有几种类型符合协议,并且动态地传入不同条件下的泛型函数.如上图所示,按下不同的按钮会将参数的不同类型(StructA或StructB)传递给函数,在这种情况下,通用特化仍然可以工作吗?

bor*_*ero 9

实际上有一个来自今年WWDC的视频(关于类,结构和协议的性能;我没有链接,但你应该能够找到它).

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

在您使用泛型约束的第一个函数中,编译器实际上创建了另一个函数,该函数在该类型上显式执行函数的操作.(如果你使用这个函数有很多不同的类型,你的代码大小可能会显着增加;请参阅C++代码膨胀以供进一步参考.)但是,编译器现在可以静态调度方法,如果可能的话内联函数并确实不必分配任何堆空间.在上面提到的视频中,代码大小不必显着增加,因为代码仍然可以共享...所以具有通用约束的函数肯定是要走的路!

  • 非常感谢您。您确实提供了很好的解释和有用的信息,[此精彩演讲](https://developer.apple.com/videos/play/wwdc2016/416/)详细介绍了我的问题,其中提到了有关泛型专业化的问题(这是肉)。 (2认同)