Swift & UI 进阶 20m
拥抱 Swift 泛型
Embrace Swift generics
2022年6月6日
一句话判断
从具体类型到泛型抽象的完整工作流——以一个农场模拟为案例,Swift 编译器团队手把手教你识别重复模式、提取公共接口、写出优雅的泛型代码。
这场 Session 讲了什么
Swift 编译器团队的 Holly 以一个农场模拟程序为例,系统讲解了 Swift 泛型的设计工作流。从具体类型(Cow、Horse、Chicken)出发,识别重复代码模式,提取公共接口(协议),最终用泛型统一实现。
Session 覆盖了泛型设计的四个步骤:用具体类型建模、识别公共能力、构建接口表示这些能力、用泛型代码基于接口编写通用逻辑。核心观点是:当你发现自己在写重复的重载函数时,就该考虑泛型了。
值得深挖的点
重复重载是泛型的信号。Session 展示了一个 Farm 结构体,为 Cow、Horse、Chicken 分别写了几乎相同的 feed 方法。每个重载的实现逻辑一样(种植饲料、收获、喂养),只是类型不同。这就是泛型化的时机。
协议作为接口抽象。将公共能力提取到协议中:Animal 协议(有 eat 方法)、Feed 协议(对应 Animal 的饲料类型)。通过关联类型(associatedtype)建立类型间的关系——每种动物有对应的饲料类型。
泛型函数的逐步推导。从 func feed(cow: Cow) 到 func feed<A: Animal>(animal: A),Swift 的类型推断让调用方代码无需指定泛型参数。编译器根据传入的具体类型自动推导。
some 和 any 关键字的区别。Swift 5.7 进一步明确了 some(不透明返回类型,编译器知道具体类型)和 any(存在类型,运行时多态)的使用场景。泛型函数参数优先用 some,需要运行时多态时才用 any。
代码片段
// 重构前:重复的重载
struct Farm {
func feed(_ animal: Cow) {
let crop = Alfalfa.grow()
let hay = crop.harvest()
animal.eat(hay)
}
func feed(_ animal: Horse) {
let crop = Oat.grow()
let food = crop.harvest()
animal.eat(food)
}
// 每增加一种动物就要加一个重载...
}
// 重构后:协议 + 泛型
protocol Animal {
associatedtype FeedType: Feed
func eat(_ food: FeedType)
}
protocol Feed {
associatedtype CropType: Crop
static func grow() -> CropType
}
protocol Crop {
associatedType FeedType: Feed
func harvest() -> FeedType
}
struct Farm {
func feed<A: Animal>(_ animal: A) {
let crop = A.FeedType.grow()
let food = crop.harvest()
animal.eat(food)
}
}
// 调用时代码一样简洁,类型自动推导
let farm = Farm()
farm.feed(cow) // 推导 A = Cow
farm.feed(horse) // 推导 A = Horse
farm.feed(chicken) // 推导 A = Chicken
最佳实践
- 当你写了三个以上签名相似的重载函数,立刻考虑泛型化
- 协议设计时用关联类型建立类型间的关系,比用泛型参数更自然
- 泛型函数的约束要精确——只要求你真正需要的协议,不要过度约束
- 优先使用 some 而非 any 作为返回类型,保留编译器的优化空间
- 从具体类型开始,逐步抽象——不要一上来就设计过度抽象的泛型层级
还有什么值得关注
- Swift 5.7 对泛型做了大量语法简化(如省略 where 子句的写法)
- some 关键字现在可以用在函数参数位置,进一步减少泛型语法的视觉负担
- 协议的关联类型可以有默认值,降低遵循协议的门槛
- 配套 Session “Design protocol interfaces in Swift” 深入讲解协议设计
WWDC 2022