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,代码短一半、可读性翻倍。
相关推荐
初级代码游戏7 小时前
iOS开发 SwiftUI 14:ScrollView 滚动视图
ios·swiftui·swift
初级代码游戏9 小时前
iOS开发 SwitftUI 13:提示、弹窗、上下文菜单
ios·swiftui·swift·弹窗·消息框
zhyongrui12 小时前
托盘删除手势与引导体验修复:滚动冲突、画布消失动画、气泡边框
ios·性能优化·swiftui·swift
zhangfeng113315 小时前
CSDN星图 支持大模型微调 trl axolotl Unsloth 趋动云 LLaMA-Factory Unsloth ms-swift 模型训练
服务器·人工智能·swift
zhyongrui2 天前
SnipTrip 发热优化实战:从 60Hz 到 30Hz 的性能之旅
ios·swiftui·swift
大熊猫侯佩2 天前
Neo-Cupertino 档案:撕开 Actor 的伪装,回归 Non-Sendable 的暴力美学
swift·observable·actor·concurrency·sendable·nonsendable·data race
2501_915921434 天前
在没有源码的前提下,怎么对 Swift 做混淆,IPA 混淆
android·开发语言·ios·小程序·uni-app·iphone·swift
00后程序员张4 天前
对比 Ipa Guard 与 Swift Shield 在 iOS 应用安全处理中的使用差异
android·开发语言·ios·小程序·uni-app·iphone·swift
大熊猫侯佩5 天前
星际穿越:SwiftUI 如何让 ForEach 遍历异构数据(Heterogeneous)集合
swiftui·swift·遍历·foreach·any·异构集合·heterogeneous
hjs_deeplearning5 天前
认知篇#15:ms-swift微调中gradient_accumulation_steps和warmup_ratio等参数的意义与设置
开发语言·人工智能·机器学习·swift·vlm