Swift 泛型(Generics)详解
Swift 的泛型(Generics)是类型安全的参数化编程机制,让你编写灵活且可复用的函数、方法、类、结构体、枚举,而无需为每种类型重复代码。
1. 为什么需要泛型?
// 没有泛型:重复代码
func swapInts(_ a: inout Int, _ b: inout Int) { ... }
func swapStrings(_ a: inout String, _ b: inout String) { ... }
func swapDoubles(_ a: inout Double, _ b: inout Double) { ... }
使用泛型:
func swap<T>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
2. 泛型函数
func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? {
for (index, item) in array.enumerated() {
if item == value {
return index
}
}
return nil
}
// 使用
let index = findIndex(of: "Swift", in: ["Java", "Swift", "Kotlin"])
T: Equatable表示 T 必须实现==
3. 泛型类型(结构体、类、枚举)
泛型栈(Stack)
struct Stack<Element> {
private var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element? {
items.popLast()
}
var top: Element? {
items.last
}
}
// 使用
var intStack = Stack<Int>()
intStack.push(5)
intStack.push(10)
var stringStack = Stack<String>()
stringStack.push("Hello")
4. 泛型协议(Type Erasure & Associated Types)
问题:不能直接用协议作为类型(存在类型开销)
protocol Animal {
func speak()
}
不能这样写(Swift 5.6 以前):
let animals: [Animal] = [...] // 错误:协议不能作为具体类型
解决:关联类型(Associated Type)
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
subscript(i: Int) -> Item { get }
}
实现:
struct IntStack: Container {
typealias Item = Int // 可省略,编译器推断
private var items: [Int] = []
mutating func append(_ item: Int) { items.append(item) }
var count: Int { items.count }
subscript(i: Int) -> Int { items[i] }
}
5. where 子句:约束增强
func allItemsMatch<C1: Container, C2: Container>(
_ c1: C1, _ c2: C2
) -> Bool where C1.Item == C2.Item, C1.Item: Equatable {
if c1.count != c2.count { return false }
for i in 0..<c1.count {
if c1[i] != c2[i] { return false }
}
return true
}
6. 泛型扩展(Conditional Extensions)
extension Stack where Element: Equatable {
func contains(_ item: Element) -> Bool {
items.contains(item)
}
}
// 仅当 Element 是 Equatable 时才有 contains
7. any 和 some 关键字(Swift 5.7+)
| 关键字 | 含义 |
|---|---|
some Protocol | 不透明类型:某个具体类型,但隐藏具体类型 |
any Protocol | 存在类型:任意符合协议的类型(类型擦除) |
some 示例(返回值)
func makeStack() -> some Container {
return Stack<Int>() // 编译器知道是 Stack<Int>,但调用者只知道是 Container
}
调用者不能访问
Stack特有方法,但类型安全。
any 示例(存储)
var containers: [any Container] = [
Stack<Int>(),
[1, 2, 3] as Array<Int>
]
运行时类型擦除,性能略低,适合异构集合。
8. 类型擦除(Type Erasure)
当你想用协议作为类型但避免 any 性能开销:
final class AnyBox<T>: Box {
private let _value: () -> T
init<U: Box>(_ base: U) where U.Value == T {
_value = { base.value }
}
var value: T { _value() }
}
高级用法,通常用
any替代。
9. 泛型与协议组合(POP 核心)
protocol Repository {
associatedtype Model: Codable
func save(_ model: Model)
func load() -> [Model]
}
struct UserRepository: Repository {
func save(_ model: User) { ... }
func load() -> [User] { ... }
}
10. 常用泛型系统类型
| 类型 | 说明 |
|---|---|
Optional<T> | T? |
Array<Element> | [Element] |
Dictionary<Key: Hashable, Value> | [Key: Value] |
Result<Success, Failure> | 异步结果封装 |
AnyPublisher<Output, Failure> | Combine 框架 |
11. 泛型实战:JSON 解析器
struct JSONDecoder<T: Decodable> {
func decode(from data: Data) throws -> T {
return try JSONDecoder().decode(T.self, from: data)
}
}
// 使用
let user: User = try JSONDecoder<User>().decode(from: data)
12. 性能提示
| 场景 | 建议 |
|---|---|
频繁使用 any Protocol | 可能有动态派发开销 |
| 泛型函数 | 编译时专特化,零开销 |
where 约束 | 优先于 Any |
推荐实践(Best Practices)
// 1. 优先泛型而非 Any
func process<T>(_ items: [T]) { ... }
// 2. 小而专注的协议 + 泛型
protocol Fetchable { associatedtype ID; func fetch(id: ID) -> Self? }
// 3. 使用 where 提高可读性
extension Array where Element: Hashable { ... }
// 4. 避免过度泛型
// 坏:func log<T>(_ value: T) { print(value) }
// 好:func log(_ value: CustomStringConvertible)
泛型 vs 协议 vs 继承
| 特性 | 泛型 | 协议 | 继承 |
|---|---|---|---|
| 类型安全 | 是 | 是 | 是 |
| 代码复用 | 高 | 高 | 中 |
| 运行时开销 | 低 | 中(any) | 低 |
| 灵活性 | 高 | 高 | 低 |
Swift 推荐:面向协议编程 + 泛型(POP + Generics)
面试高频题
- 泛型函数如何约束多个类型相同?
where T == U
somevsany的区别?
some: 隐藏具体类型,编译时确定any: 类型擦除,运行时动态
- 如何让协议有默认实现?
extension Protocol { func method() { ... } }
需要 泛型 + 协议实战项目?如:
- 通用网络层(
APIClient<T: Decodable>) - 依赖注入容器(
Resolver<T>) - 状态管理(
Store<State, Action>)
欢迎继续提问!