为什么"关联类型"是协议的分水岭
在上面,我们接触的协议都属于"无关联类型协议"------编译期无需知道协议里的泛型占位符具体是什么。
一旦协议里出现了 associatedtype,它就不再是普通类型,而变成了协议泛型:只能当作约束使用,不能当作变量/参数/返回值类型直接出现。
关联类型基础语法
swift
protocol Container {
associatedtype Item // ① 声明一个占位符
var count: Int { get }
mutating func append(_ item: Item) // ② 在方法里使用占位符
subscript(index: Int) -> Item { get } // ③ 在下标里使用占位符
}
实现者决定真实类型
swift
struct IntStack: Container {
private var items = [Int]()
typealias Item = Int // 可省略,编译器自动推断
mutating func append(_ item: Int) { items.append(item) }
subscript(index: Int) -> Int { items[index] }
var count: Int { items.count }
}
struct StringQueue: Container {
private var items = [String]()
// 不写 typealias,编译器也能从 append 参数推断 Item == String
mutating func append(_ item: String) { items.append(item) }
subscript(index: Int) -> String { items[index] }
var count: Int { items.count }
}
带约束的关联类型
swift
// 继承Container,只接收Item是Numeric的类型
protocol SummableContainer: Container where Item: Numeric {
func total() -> Item
}
extension SummableContainer {
func total() -> Item {
var sum: Item = 0
for i in 0..<count { sum = sum + self[i] }
return sum
}
}
使用
swift
extension IntStack: SummableContainer {} // Int 符合 Numeric,自动获得 total()
var s = IntStack()
s.append(1)
s.append(2)
print(s.total()) // 3
Self 出现在协议里:返回自身类型的需求
swift
protocol Copyable {
func copy() -> Self // 要求返回"真实类型"本身
}
class Dog: Copyable {
var name = ""
required init() {} // 必须保证子类也能初始化
func copy() -> Self {
let new = Self() // Self 在运行期代表真实动态类型
new.name = name
return new
}
}
let husky = Dog()
husky.name = "Husky"
let another = husky.copy() // 类型推断为 Dog
注意
Self只能出现在"协议内"或"协议扩展"中,不能出现在普通结构体/类的方法签名里。- 如果协议用
associatedtype,Self就是该类型的别名。
泛型递归约束:where 子句的魔法
swift
protocol Node {
associatedtype Value
var value: Value { get }
var children: [Self] { get } // 递归引用自身
}
extension Node where Value: Numeric {
func sumTree() -> Value {
value + children.reduce(0) { $0 + $1.sumTree() }
}
}
struct IntNode: Node {
let value: Int
let children: [IntNode]
}
let tree = IntNode(value: 10, children: [
IntNode(value: 3, children: []),
IntNode(value: 5, children: [
IntNode(value: 1, children: [])
])
])
print(tree.sumTree()) // 10+3+5+1 = 19
真实案例:Swift 标准库里的 Sequence
swift
protocol Sequence {
associatedtype Element
associatedtype Iterator: IteratorProtocol where Iterator.Element == Element
__consuming func makeIterator() -> Iterator
}
protocol IteratorProtocol {
associatedtype Element
mutating func next() -> Element?
}
自定义序列
swift
struct Fibonacci: Sequence {
struct Iterator: IteratorProtocol {
var a = 0, b = 1
mutating func next() -> Int? {
let next = a
a = b
b = next + a
return next
}
}
func makeIterator() -> Iterator { Iterator() }
}
for (i, n) in Fibonacci().enumerated().prefix(10) {
print("F\(i) = \(n)")
}
类型擦除(Type Erasure):解决"关联类型协议不能当返回类型"
swift
protocol Feed {
associatedtype Item
func next() -> Item?
}
// 1. 包装类擦除具体 Item 类型
struct AnyFeed<Item>: Feed {
private var _next: () -> Item?
init<F: Feed>(_ real: F) where F.Item == Item {
_next = { real.next() }
}
func next() -> Item? { _next() }
}
// 2. 使用处不再出现关联类型
func makeIntFeed() -> AnyFeed<Int> {
let fib = Fibonacci().makeIterator()
return AnyFeed(IteratorFeed(fib))
}
标准库已有
AnySequence<Element>AnyPublisher<Output, Failure>(Combine)AnyView(SwiftUI)
实战技巧清单
| 技巧场景 | 具体实现技巧 |
|---|---|
| 想返回"某个遵守协议的对象" | 用泛型 <T: Protocol> 或 some Protocol |
协议中定义 associatedtype |
仅能作为类型约束,无法直接当作变量类型;需通过"类型擦除"后暴露 |
| 要求子类必须实现指定初始化器 | 协议中声明 init(),类侧配合写 required init() |
| 让扩展方法仅对满足部分约束的类型可见 | 使用 where 子句,例如 extension Container where Item: Comparable |
禁止类型隐式标记为 Sendable |
声明 struct File: ~Sendable,或通过 @available(*, unavailable) extension File: Sendable 禁用 |