跳到主要内容

泛型

问题

Swift 的泛型是什么?如何使用泛型约束?关联类型与泛型的关系是什么?

答案

泛型函数

泛型让你编写类型安全且可复用的代码:

// 没有泛型:需要为每种类型写一个函数
func swapInts(_ a: inout Int, _ b: inout Int) { let t = a; a = b; b = t }
func swapStrings(_ a: inout String, _ b: inout String) { let t = a; a = b; b = t }

// ✅ 使用泛型:一个函数搞定所有类型
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}

var x = 1, y = 2
swapValues(&x, &y) // x=2, y=1

泛型类型

// 泛型栈
struct Stack<Element> {
private var items: [Element] = []

var isEmpty: Bool { items.isEmpty }
var count: Int { items.count }

mutating func push(_ item: Element) {
items.append(item)
}

mutating func pop() -> Element? {
items.popLast()
}

func peek() -> Element? {
items.last
}
}

var intStack = Stack<Int>()
intStack.push(1)
intStack.push(2)
intStack.pop() // Optional(2)

var stringStack = Stack<String>()
stringStack.push("hello")

泛型约束

通过 :where 子句限制泛型参数:

// 约束:T 必须遵循 Comparable
func findMin<T: Comparable>(_ array: [T]) -> T? {
guard var min = array.first else { return nil }
for item in array {
if item < min { min = item }
}
return min
}

findMin([3, 1, 4, 1, 5]) // Optional(1)
findMin(["c", "a", "b"]) // Optional("a")

// where 子句约束
func allEqual<T: Equatable>(_ array: [T]) -> Bool where T: Hashable {
return Set(array).count <= 1
}

// 多个泛型参数 + 约束
func merge<T: Sequence, U: Sequence>(_ a: T, _ b: U) -> [T.Element]
where T.Element == U.Element {
return Array(a) + Array(b)
}

泛型扩展

extension Stack where Element: Equatable {
func contains(_ item: Element) -> Bool {
items.contains(item)
}
}

extension Stack where Element: CustomStringConvertible {
var description: String {
items.map { $0.description }.joined(separator: ", ")
}
}

关联类型(Associated Types)

在协议中使用泛型的方式:

protocol Repository {
associatedtype Model: Identifiable
associatedtype ID

func findById(_ id: ID) -> Model?
func save(_ model: Model)
func deleteById(_ id: ID)
}

// 遵循时确定具体类型
struct User: Identifiable {
let id: UUID
var name: String
}

class UserRepository: Repository {
// 编译器推断 Model = User, ID = UUID
private var storage: [UUID: User] = [:]

func findById(_ id: UUID) -> User? { storage[id] }
func save(_ model: User) { storage[model.id] = model }
func deleteById(_ id: UUID) { storage[id] = nil }
}

类型擦除(Type Erasure)

带关联类型的协议不能直接作为类型使用:

protocol AnyIterator {
associatedtype Element
mutating func next() -> Element?
}

// ❌ 编译错误:Protocol with associatedtype can only be used as a generic constraint
// var iterator: AnyIterator

// ✅ 方式1:使用 any(Swift 5.7+)
var iterator: any AnyIterator // 存在类型

// ✅ 方式 2:手动类型擦除(传统方式)
struct AnyIteratorErased<T>: AnyIterator {
typealias Element = T
private let _next: () -> T?

init<I: AnyIterator>(_ iterator: I) where I.Element == T {
var iter = iterator
_next = { iter.next() }
}

mutating func next() -> T? { _next() }
}

不透明类型(some

// 调用者不知道具体类型,但编译器知道
func makeCollection() -> some Collection {
return [1, 2, 3] // 实际返回 Array<Int>
}

// 对比 any:运行时动态,编译器不知道具体类型
func makeAnyCollection() -> any Collection {
if Bool.random() {
return [1, 2, 3]
} else {
return Set([1, 2, 3])
}
}

泛型的特化(Specialization)

Swift 编译器对泛型进行单态化优化:

// 源码
func add<T: Numeric>(_ a: T, _ b: T) -> T { a + b }
add(1, 2) // 编译器生成 add_Int 版本
add(1.5, 2.5) // 编译器生成 add_Double 版本
性能

泛型函数在编译时会被特化为具体类型的版本(类似 C++ 模板),因此性能与手写具体类型版本相同,zero-cost abstraction。


常见面试问题

Q1: 泛型和 Any / AnyObject 有什么区别?

答案

  • 泛型:编译时类型安全,编译器知道具体类型,可以特化优化
  • Any:运行时类型擦除,可以存储任何类型,使用时需要类型转换(as?
  • AnyObject:仅限类(class)类型的 Any

泛型在编译期提供类型检查,性能更优;Any 牺牲了类型安全,应尽量避免使用。

Q2: where 子句可以放在哪些位置?

答案

// 1. 函数声明
func f<T>(_ x: T) where T: Equatable { }

// 2. 类型声明
struct S<T> where T: Codable { }

// 3. 扩展
extension Array where Element: Comparable { }

// 4. 关联类型约束
protocol P {
associatedtype Item where Item: Hashable
}

Q3: 如何理解 Swift 的类型擦除?

答案

类型擦除是将具体类型信息隐藏、只保留协议能力的技术。标准库中的 AnyHashableAnySequence 就是类型擦除的包装器。Swift 5.7+ 的 any 关键字提供了语言级别的存在类型支持,简化了手动类型擦除的需求。

Q4: some 关键字为什么比 any 性能更好?

答案

  • some(不透明类型):编译器知道具体类型,可以进行静态分发和内联优化,不需要存在容器(existential container)
  • any(存在类型):运行时通过存在容器(包含值缓冲区、元数据、PWT 指针)间接访问,需要动态分发,有堆分配开销

在性能敏感的代码中优先使用 some,在需要异构集合时使用 any

相关链接