为什么需要泛型
- 无泛型时代的"粘贴式"编程
swift
// 只能交换 Int
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temp = a
a = b
b = temp
}
// 复制粘贴,改个类型名
func swapTwoStrings(_ a: inout String, _ b: inout String) {
let temp = a
a = b
b = temp
}
问题:
- 代码完全一样,仅类型不同
- 维护 N 份副本,牵一发而动全身
泛型函数:写一次,跑所有类型
- 语法与占位符 T
swift
// <T> 声明一个"占位符类型"
// 编译器每次调用时把 T 替换成真实类型
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
- 调用方式:编译器自动推断 T
swift
var x = 3, y = 5
swapTwoValues(&x, &y) // T 被推断为 Int
var s1 = "A", s2 = "B"
swapTwoValues(&s1, &s2) // T 被推断为 String
要点:
- 占位符名可以是任意合法标识符,习惯单字母
T、U、V - 同一函数签名里所有
T必须是同一真实类型,不允许swapTwoValues(3, "hello")
泛型类型:自定义可复用容器
- 非泛型版 Int 栈
swift
struct IntStack {
private var items: [Int] = []
mutating func push(_ item: Int) { items.append(item) }
mutating func pop() -> Int { items.removeLast() }
}
- 泛型版 Stack
swift
struct Stack<Element> { // Element 为占位符
private var items: [Element] = []
mutating func push(_ item: Element) { items.append(item) }
mutating func pop() -> Element { items.removeLast() }
}
// 用法
var intStack = Stack<Int>()
intStack.push(1)
intStack.push(2)
print(intStack.pop()) // 2
var strStack = Stack<String>()
strStack.push("🍎")
strStack.push("🍌")
print(strStack.pop()) // 🍌
类型约束:给"任意类型"划边界
- 场景:在容器里查找元素索引
swift
// 编译失败版:并非所有 T 都支持 ==
func findIndex<T>(of valueToFind: T, in array: [T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind { // ❌ 错误:T 不一定能比较
return index
}
}
return nil
}
- 加入 Equatable 约束
swift
func findIndex<T: Equatable>(of valueToFind: T, in array: [T]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind { // ✅ 现在合法
return index
}
}
return nil
}
小结:
- 语法
<T: SomeProtocol>或<T: SomeClass> - 可同时约束多个占位符:
<T: Equatable, U: Hashable>
关联类型(associatedtype):协议里的"泛型"
- 定义容器协议
swift
protocol Container {
associatedtype Item // 占位名
mutating func append(_ item: Item)
var count: Int { get }
subscript(index: Int) -> Item { get }
}
- 让泛型 Stack 直接 conform
swift
extension Stack: Container {
// 编译器自动推断:
// append 参数 item 类型 = Element
// subscript 返回值类型 = Element
// 故 Item == Element,无需手写 typealias
}
- 给关联类型加约束
swift
protocol EquatableContainer: Container where Item: Equatable { }
// 现在所有 Item 必须支持 ==,可直接在扩展里使用 ==/!=
泛型 where 子句:更细粒度的约束
- 函数级 where
swift
// 检查两个容器是否完全一致(顺序、元素、数量)
func allItemsMatch<C1: Container, C2: Container>
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.Item == C2.Item, C1.Item: Equatable
{
if someContainer.count != anotherContainer.count { return false }
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] { return false }
}
return true
}
- 扩展级 where
swift
extension Stack where Element: Equatable {
// 仅当元素可比较时才出现该方法
func isTop(_ item: Element) -> Bool {
guard let top = items.last else { return false }
return top == item
}
}
- 下标也能泛型 + where
swift
extension Container {
// 接收一组索引,返回对应元素数组
subscript<Indices: Sequence>(indices: Indices) -> [Item]
where Indices.Element == Int
{
var result: [Item] = []
for index in indices {
result.append(self[index])
}
return result
}
}
隐式约束与 Copyable(Swift 5.9+)
Swift 自动为泛型参数加上 Copyable 约束,以便值能被多次使用。
若你明确允许"可复制或不可复制",用前缀 ~ 抑制隐式约束:
swift
func consumeOnce<T>(_ x: consuming T) where T: ~Copyable {
// x 只能被移动一次,不能再复制
}
示例代码
把下面代码一次性粘进 Playground,逐行跑通:
swift
import Foundation
// 1. 通用交换
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let t = a; a = b; b = t
}
// 2. 泛型栈
struct Stack<Element> {
private var items: [Element] = []
mutating func push(_ e: Element) { items.append(e) }
mutating func pop() -> Element { items.removeLast() }
}
extension Stack: Container {
typealias Item = Element
mutating func append(_ item: Element) { push(item) }
var count: Int { items.count }
subscript(i: Int) -> Element { items[i] }
}
// 3. 协议
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(index: Int) -> Item { get }
}
// 4. 扩展约束
extension Stack where Element: Equatable {
func isTop(_ e: Element) -> Bool { items.last == e }
}
// 5. 测试
var s = Stack<Int>()
s.push(10)
s.push(20)
print(s.isTop(20)) // true
print(s.pop()) // 20
var a: [String] = ["A","B","C"]
var b: Array<String> = ["A","B","C"]
print(allItemsMatch(a, b)) // true
总结与实战扩展
-
泛型 = 代码的"模板引擎"
把"类型"也当成参数,一次性书写,多处复用,减少 BUG
-
约束是"接口隔离"的利器
与其写长篇文档说明"请传能比较的值",不如让编译器帮你拦下来:
<T: Equatable>。 -
关联类型让协议"带泛型"
协议不再只能定义"行为",还能定义"元素类型",使协议与泛型容器无缝衔接。
-
where 子句是"需求说明书"
函数、扩展、下标都能写 where,把"调用前提"写进类型系统,用不合法的状态直接编译不过,比运行时断言更早暴露问题。
-
实战场景举例
- JSON 解析层:写一个
Decoder<T: Decodable>,一份代码支持所有模型
-缓存框架:定义
Cache<Key: Hashable, Value>,Key 约束为 Hashable,Value 任意类型- 网络请求:
Request<Resp: Decodable>,响应体自动解析为任意模型
- JSON 解析层:写一个