Swift 协议(Protocol)指南(二):关联类型、Self 约束与泛型递归,一次彻底搞懂

为什么"关联类型"是协议的分水岭

在上面,我们接触的协议都属于"无关联类型协议"------编译期无需知道协议里的泛型占位符具体是什么。

一旦协议里出现了 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 只能出现在"协议内"或"协议扩展"中,不能出现在普通结构体/类的方法签名里。
  • 如果协议用 associatedtypeSelf 就是该类型的别名。

泛型递归约束: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 禁用
相关推荐
HarderCoder2 小时前
Swift 协议(Protocol)指南(一):从语法到实战
swift
HarderCoder2 小时前
Swift TaskGroup 结果顺序踩坑指南:为什么返回顺序和创建顺序不一致,以及最通用的修复办法
swift
Swift社区10 小时前
iOS 基于 Foundation Model 构建媒体流
ios·iphone·swift·媒体
大熊猫侯佩20 小时前
侠客行・iOS 26 Liquid Glass TabBar 破阵记
ios·swiftui·swift
qixingchao2 天前
iOS SwiftUI 动画开发指南
ios·swiftui·swift
大熊猫侯佩2 天前
猿族代码战记:Mutex 升级版——守护 Swift 并发的“香蕉仓库”
swiftui·swift·apple
大熊猫侯佩2 天前
Thread.sleep 与 Task.sleep 终极对决:Swift 并发世界的 “魔法休眠术” 揭秘
ios·swift·apple
大熊猫侯佩2 天前
【大话码游之 Observation 传说】下集:破咒终局了,天眼定乾坤
ios·swift·apple