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,代码短一半、可读性翻倍。
相关推荐
报错小能手5 小时前
ios开发方向——对于实习开发的app(Robopocket)讲解
开发语言·学习·ios·swift
茶底世界之下9 小时前
Harbeth:高性能Metal图像处理库,让你的图片处理速度飞起来!
前端·github·swift
风舞雪凌月10 小时前
【趣谈】移动系统和桌面系统编程语言思考
java·c语言·c++·python·学习·objective-c·swift
UXbot1 天前
AI App 设计生成工具哪个好?
ui·kotlin·软件构建·产品经理·ai编程·swift
黄林晴1 天前
Swift 杀进 Android,Google 和 Apple 都要失眠了?
android·前端·swift
东坡肘子1 天前
一墙之隔,不同的时空 -- 肘子的 Swift 周报 #129
人工智能·swiftui·swift
harder3212 天前
Swift 面向协议编程的 RMP 模式
开发语言·ios·mvc·swift·策略模式
KevinCyao4 天前
iOS短信营销接口示例代码:Swift/Xcode集成营销短信API的完整开发教程
ios·swift
2501_916007475 天前
iOS 开发工具有哪些 按开发流程整理的工具清单
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程