泛型基础知识

泛型是类型的占位符,允许你编写可以跨多种类型应用的灵活代码。使用泛型优于 Any的优点是它们仍然允许编译器强制实现强类型安全性。

通用占位符在尖括号 <> 中定义。

通用函数

对于函数 ,此占位符位于函数名称后面:

/// Picks one of the inputs at random, and returns it
func pickRandom<T>(_ a:T, _ b:T) -> T {
    return arc4random_uniform(2) == 0 ? a : b
}

在这种情况下,通用占位符是 T。当你来调用函数时,Swift 可以为你推断 T 的类型(因为它只是作为实际类型的占位符)。

let randomOutput = pickRandom(5, 7) // returns an Int (that's either 5 or 7)

这里我们将两个整数传递给函数。因此 Swift 推断 T == Int - 因此函数签名被推断为 (Int, Int) -> Int

由于泛型提供了强大的类型安全性 - 函数的参数和返回必须是相同的类型。因此以下内容不会编译:

struct Foo {}

let foo = Foo()

let randomOutput = pickRandom(foo, 5) // error: cannot convert value of type 'Int' to expected argument type 'Foo'

通用类型

为了将泛型与结构枚举一起使用 ,可以在类型名称后定义通用占位符。

class Bar<T> {
    var baz : T
    
    init(baz:T) {
        self.baz = baz
    }
}

当你使用 Bar 类时,此通用占位符将需要一个类型。在这种情况下,可以从初始化 init(baz:T) 推断出。

let bar = Bar(baz: "a string") // bar's type is Bar<String>

这里通用占位符 T 被推断为 String 类型,因此创建了 Bar<String> 实例。你还可以明确指定类型:

let bar = Bar<String>(baz: "a string")

与类型一起使用时,给定的通用占位符将在给定实例的整个生命周期内保持其类型,并且在初始化后不能更改。因此,当你访问属性 baz 时,对于此给定实例,它将始终为 String 类型。

let str = bar.baz // of type String

传递通用类型

当你来传递泛型类型时,在大多数情况下,你必须明确你期望的通用占位符类型。例如,作为函数输入:

func takeABarInt(bar:Bar<Int>) {
    ...
}

此功能只接受 Bar<Int>。尝试传入通用占位符类型不是 IntBar 实例将导致编译器错误。

通用占位符命名

通用占位符名称不仅限于单个字母。如果给定的占位符表示有意义的概念,则应为其指定一个描述性名称。例如,Swift 的 Array 有一个名为 Element 的通用占位符,它定义了给定 Array 实例的元素类型。

public struct Array<Element> : RandomAccessCollection, MutableCollection {
    ...
}