Swift 协议(Protocol)指南(三):Primary Associated Type、some/any 与泛型式协议实战

为什么 Swift 5.7 再次"颠覆"协议

在 Swift 5.7 之前,带关联类型的协议只能当约束 <T: Sequence>,不能当类型 Sequence

这导致两个老大难:

  1. 声明变量/参数/返回值时,必须再包一层类型擦除(AnySequence<Int>)。
  2. 泛型函数无法"一眼看出"序列里到底是什么元素。

Swift 5.7 一次性给出三把新钥匙:

特性 关键词 解决痛点
主要关联类型 protocol Sequence<Element> 把最常用的关联类型"提级"到协议名上
不透明参数 some Sequence<Int> 直接当参数类型,编译期确定,零运行时开销
存在性类型 any Sequence<Int> 真·变量类型,运行时盒子,语法终于可读

Primary Associated Type:把"泛型参数"搬到协议头上

  1. 标准库示例
swift 复制代码
// Swift 5.7 标准库定义
protocol Sequence<Element> {
    associatedtype Element
    associatedtype Iterator: IteratorProtocol where Iterator.Element == Element
    func makeIterator() -> Iterator
}

Element 被写到协议名后面,成为主要关联类型。

于是我们可以像泛型结构体一样,直接写:

swift 复制代码
func sum(_ numbers: some Sequence<Int>) -> Int {   // ① 参数类型
    numbers.reduce(0, +)
}

let total = sum([1, 2, 3] + [4, 5, 6])            // ② 数组拼接也适用
  1. 为自己协议添加"主关联类型"
swift 复制代码
protocol Feed<Element> {          // ① 把最常用的关联类型提出来
    associatedtype Element
    mutating func next() -> Element?
}

struct IteratorFeed: Feed {
    var a = 0, b = 1
    mutating func next() -> Int? {
        let next = a
        a = b
        b = next + a
        return next
    }
}

// ② 立刻享受 some/any 语法
func makeIntFeed() -> some Feed<Int> {
    IteratorFeed()
}

var feeds: [any Feed<Int>] = []   // ③ 数组里放不同实现,无需再包 AnyFeed

规则

  • 只能把一个或多个 associatedtype 声明为"主要",不强制全部。
  • 主要关联类型顺序不影响使用,但建议按"常用度"排序。
  • 一旦声明,协议就获得"泛型形参"资格,可直接写 Feed<Int>

some vs. any:一句话区分

维度 some P any P
语义 编译期确定的某个具体类型 运行时存在的任何具体类型
内存布局 无间接寻址,内联存储 存在性容器(Box),通过指针引用
主要用法 返回值、泛型约束 变量、集合元素、函数参数
性能 零额外开销 轻微运行时开销(Box管理)
使用限制 不能用于var/集合类型声明 无显式限制

示例对比

swift 复制代码
protocol Feed<Element> {          // ① 把最常用的关联类型提出来
    associatedtype Element
    mutating func next() -> Element?
}

struct IteratorFeed<Iterator: IteratorProtocol>: Feed  {
    var iterator: Iterator
    init(_ iterator: Iterator) {
        self.iterator = iterator
    }
    mutating func next() -> Iterator.Element? {
        iterator.next()
    }
}

// 1. 返回值:编译期就知道真实类型
func uniqueElements<S: Sequence>(_ seq: S) -> some Sequence<S.Element>
where S.Element: Hashable & Comparable {
    Set(seq).sorted()
}

// 2. 数组:运行期才知道真实类型
var parsers: [any Feed<Int>] = [
    IteratorFeed([1, 2, 3].makeIterator()),
    IteratorFeed(stride(from: 0, to: 10, by: 2).makeIterator())
]

实战:一行代码写"泛型" SwiftUI View

swift 复制代码
import Charts

struct ChartView<Data: RandomAccessCollection>: View where Data.Element == Double {
    let data: Data
    var body: some View {
        // 老写法:调用方必须写冗长泛型参数
    }
}

// ✅ 新写法:直接不透明返回,调用方无感知
func makeChart(_ data: some RandomAccessCollection<Double>) -> some View {
    
    Chart {
        ForEach(data.enumerated(), id: \.offset) { idx,value in
            LineMark(x: .value("x", idx), y: .value("y", value))
        }
    }
}

迁移旧代码:把 AnySequence 换成 any Sequence

旧代码 新代码
AnySequence<Int> any Sequence<Int>
AnyPublisher<Int, Never> any Publisher<Int, Never>
AnyView any View(iOS 17+ 可用,仍需权衡性能)

步骤

  1. 在协议名后加 <Element>
  2. eraseToAnyPublisher() / AnySequence(...) 改成 any Sequence<Int>
  3. 若返回值无需运行时多态,直接用 some Sequence<Int> 获得零开销。

性能与二进制大小小贴士

  • some P<T> 完全静态派发,零额外内存。
  • any P<T> 引入存在性容器,16 字节 inline + 外挂堆(若值过大)。
  • 滥用 any 会让二进制出现大量"协议见证表",release 模式下编译器会优化,但debug 增量编译可能变慢。
  • 对 SwiftUI body 这种超高频调用,优先 some View;只在需要运行时异构数组时才用 any View

总结

关键词 适用场景 记忆口诀
protocol P<Element> 给自己协议"提级" "协议也能带泛参"
some P<T> 返回值、泛型约束 "编译期就确定"
any P<T> 变量、数组、字典 "运行期多态盒子"

一句话总结

Swift 5.7 让协议第一次真正拥有了"泛型形参"能力:

  • 写库的人:给协议加 <Element>,调用方立刻享受 some/any 语法糖。
  • 写业务的人:用 some Sequence<Int> 替代冗长泛型约束,用 any Sequence<Int> 替代 AnySequence,代码短一半、可读性翻倍。
相关推荐
HarderCoder3 小时前
Swift 协议(Protocol)指南(二):关联类型、Self 约束与泛型递归,一次彻底搞懂
swift
HarderCoder3 小时前
Swift 协议(Protocol)指南(一):从语法到实战
swift
HarderCoder3 小时前
Swift TaskGroup 结果顺序踩坑指南:为什么返回顺序和创建顺序不一致,以及最通用的修复办法
swift
Swift社区11 小时前
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