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

D.C*_*.C. 7 generics swift

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

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

为什么不这样写呢?

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

谢谢你的解释.


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

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

Air*_*ity 8

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

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

func f<T: Printable>(t: T) {
    // do some stuff
}
Run Code Online (Sandbox Code Playgroud)

不同于此:

func g(p: Printable) {
    // do some stuff
}
Run Code Online (Sandbox Code Playgroud)

不同的是,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)
}
Run Code Online (Sandbox Code Playgroud)

例如:

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!"
Run Code Online (Sandbox Code Playgroud)

你可以通过很少的方式看到差异:

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

f(1 as Int8)  // prints 1
f(1 as Int64) // prints 8
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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


Gre*_*ley 5

在协议的情况下,它取决于协议本身.如果协议使用Self或者a typealias,则不能直接使用.对于任何其他协议,您可以声明类型的变量和参数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)
}
Run Code Online (Sandbox Code Playgroud)

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

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