了解swift泛型与将参数视为协议或基本类型

时间:2014-12-23 23:01:18

标签: generics swift

有人可以帮我理解使用泛型而不仅仅使用基类或协议的好处吗?也许我只需要阅读Swift指南几次,但是泛型的概念并没有下沉。考虑使用泛型的这个例子

func removeObject<T : Equatable>(object: T, inout fromArray array: [T]) 
{
    var index = find(array, object)
    array.removeAtIndex(index!)
}

为什么不这样写呢?

// As pointed out, this does not compile. I was more-so curious as to why
func removeObject(object: Equatable, inout fromArray array: [Equatable]) 
{
    var index = find(array, object)
    array.removeAtIndex(index!)
}

感谢您的解释。


更新。是的,澄清我的例子完全是假设的。我正在考虑如何在Objective-C中实现这个问题。在那,我只是传递类型id的参数,这将做到这一点。

我的问题是为了深入了解为什么在Swift中不允许使用类似的模式,而是使用泛型来代替。

3 个答案:

答案 0 :(得分:7)

我认为Swift团队已经发布了一篇关于如何能够将第二个示例作为第一个示例的简写的开发论坛帖子。但是,我认为它仍然是一个通用函数 - 只是一个声明一个的简写?

那有什么不同?正如其他答案所指出的那样,Equatable只能用于一般。但是,让我们举一个不必的例子。这是怎么回事:

func f<T: Printable>(t: T) {
    // do some stuff
}

与此不同:

func g(p: Printable) {
    // do some stuff
}

不同之处在于,f定义了在编译时生成的一系列函数,无论传递的内容是t替换为T的所有类型。*所以如果你传递了一个Int,就好像你写了一个版本func f(t: Int) { … }。如果您传入了Double,就像写func f(t: Double) { … }

一样

*这是一个过度简化,但现在就用它...

另一方面,g只是一个函数,在运行时只能接受对Printable协议的引用。

在实践中,差异几乎难以察觉。例如,如果您将t内的f传递给另一个函数,则其行为如下:

func f(i: Int) {
    // h doesn’t receive an Int 
    // but a Printable:
    h(i as Printable)
}

例如:

func h(i: Int) {
    println("An Int!")
}

func h(p: Printable) {
    println("A Printable!")
}

func f<T: Printable>(t: T) {
    h(t)
}

h(1) // prints "An Int!"
f(1) // prints "A Printable!"

你可以通过以下方式看到差异:

func f<T: Printable>(t: T) { 
    println(sizeof(t))
}

f(1 as Int8)  // prints 1
f(1 as Int64) // prints 8

最大的区别是他们可以返回实际的泛型类型而不是协议:

func f<T: Printable>(t: T) -> T {
    return t
}

func g(p: Printable) -> Printable {
    return p
}

let a = f(1)    // a is an Int
let b = f([1])  // b is an [Int]

let c = g(1)    // c is a Printable
let d = g([1])  // d is a Printable

最后一个示例是理解为什么具有关联类型的协议只能一般使用的关键。假设您想要自己实现first

func first<C: CollectionType>(x: C) -> C.Generator.Element? {
    if x.startIndex != x.endIndex {
        return x[x.startIndex]
    }
    else {
        return nil
    }
}

如果first不是泛型函数,而只是一个接收CollectionType协议参数的常规函数​​,那么如何改变它返回的内容呢?

答案 1 :(得分:5)

在协议的情况下,它取决于协议本身。如果协议使用Selftypealias,则无法直接使用。对于任何其他协议,您可以声明protocol<MyProtocol>类型的变量和参数,例如var o: protocol<MyProtocol>

您不能说var o: protocol<Equatable>的原因是因为Equatable协议的设计方式必须满足它声明的某些约束(在本例中为Self),因此它只能用作泛型类型约束。换句话说,编译器必须能够在编译时找出 Self关于任何Equatable的内容,并且它不能(总是)在var o: protocol<Equatable>

为什么使用泛型而不是协议或基类?因为泛型可以比那些仍然是类型安全的更通用。这特别有用,例如,回调之类的东西。这是一个非常人为的例子:

class Useless<T> {
    private let o: T
    private let callback: (T, String) -> Void
    required init(o: T, callback: (T, String) -> Void) {
        self.o = o
        self.callback = callback
    }
    func publish(message: String) {
        callback(o, message)
    }
}

var useless = Useless(o: myObject) { obj, message in
   // Here in the callback I get type safety. 
   obj.someMethod(message)
}

(此代码从未被任何人运行过。它应被视为伪代码。)

现在,由于很多原因,这是一个非常愚蠢的例子,但它说明了这一点。多亏了泛型,回调的obj参数完全是类型安全的。这无法通过基类或协议来完成,因为我们永远无法预测回调中可能会调用哪些代码。 Useless类可以将任何类型作为其T

答案 2 :(得分:0)

您的示例并不是一个好的示例,第二个示例无法编译,因为Equatable协议具有对Self的引用,因此不能用作参数类型。

另一个主要好处是每个通用标识符都将该类型的所有参数约束为相同类型。因此,在您的第一个示例中,objectfromArray必须属于同一类型,这是您的第二个示例所不具备的。