本节摘要:泛型把"类型"变成参数:一套代码服务所有类型。本节从泛型函数讲到泛型类型(缓存与栈),再用类型约束解释"要求能力才有功能",最后落到关联类型协议。结论先行:泛型的本质是把"结构相同、类型不同"这一事实交给编译器记账,复用发生在编译期,运行时不多花一分钱。
机关房开工。先看没有机关时的痛:排片界面要缓存余票数(Int),影讯页要缓存标题(String),于是同一段逻辑被抄了两遍。
// 没有机关:每个类型抄一遍 func firstInt(_ items: [Int]) -> Int? { items.first } func firstString(_ items: [String]) -> String? { items.first } // 装上机关:T 是类型参数,调用处的实际类型决定 T func first<T>(_ items: [T]) -> T? { return items.isEmpty ? nil : items[0] } let film = first(["雾中灯塔", "星际列车"]) // T 被推断为 String let seats = first([12, 8, 3]) // T 被推断为 Int print(film ?? "-", seats ?? 0) // 输出:Optional("雾中灯塔") Optional(12)
<T> 声明"这里有个类型参数",调用时无需写明——实参类型自动代入(类型推断又一次上岗,见 1.2 节)。两个旧函数可以删掉了,将来缓存任何新模型,这台机关照单全收。
容器是最经典的泛型类型。给排片界面造一个带容量上限的通用缓存:
struct BoundedCache<Element> { // Element:类型的占位名,按语义起名 private var items: [Element] = [] let limit: Int init(limit: Int) { self.limit = limit } mutating func push(_ item: Element) { items.append(item) if items.count > limit { items.removeFirst() // 超额挤掉最老的 } } func latest() -> Element? { items.last } var count: Int { items.count } } var seatLog = BoundedCache<Int>(limit: 3) // 挤掉过期余票记录 seatLog.push(42); seatLog.push(38); seatLog.push(31); seatLog.push(27) print(seatLog.latest() ?? 0) // 输出:27 print(seatLog.count) // 输出:3(上限生效) var searchLog = BoundedCache<String>(limit: 2) // 同一台机关 服务字符串 searchLog.push("雾中"); searchLog.push("灯塔"); searchLog.push("深海") print(searchLog.latest() ?? "-") // 输出:深海
结构体、类、枚举都能带类型参数。标准库的 Array、Dictionary 正是泛型类型——[Session] 里的 Session 就是填进 Array<Element> 的实参,前五章你一直在用机关,只是今天才进机房。

无约束的 T 只能赋值传参,干不了别的。约束声明"T 必须会什么",机关内才能调用这种能力:
// 约束为 Comparable:元素必须可比较,才能找最大 func widest<T: Comparable>(_ items: [T]) -> T? { guard var best = items.first else { return nil } for item in items where item > best { // item > best 要求 Comparable best = item } return best } print(widest([118, 142, 96]) ?? 0) // 输出:142(最长片长) print(widest(["雾中灯塔", "星际列车"]) ?? "-") // 输出:星际列车(按字典序最大) // 约束为自定义协议:排片条目必须"可摘要"才进通用展示台 protocol Summarizable2 { var summary: String { get } } func printBoard<T: Summarizable2>(_ rows: [T]) { for row in rows { print("· \(row.summary)") } // 机关内可放心调 summary } struct Film2: Summarizable2 { let name: String; let minutes: Int var summary: String { "《\(name)》\(minutes) 分钟" } } struct Combo2: Summarizable2 { let title: String; let price: Double var summary: String { "\(title) 套餐 \(price) 元" } } printBoard([Film2(name: "深海回声", minutes: 96), Combo2(title: "双人夜场", price: 99)]) // 输出: // · 《深海回声》96 分钟 // · 双人夜场套餐 99.0 元
"约束换能力"是泛型设计的核心交易:约束越多,机关内能做的事越多,但能上车的类型越少。标准库的 sorted、max 都建立在 Comparable 上,第3章的协议在泛型里以约束身份再登场。
协议声明泛型能力时不用 <T>,而用 associatedtype(关联类型)留一个"待填空":
// 数据源契约:具体装什么由遵循者填空 protocol BoardDataSource { associatedtype Item // 关联类型:遵循时确定 var allItems: [Item] { get } func title(for item: Item) -> String } struct SessionSource: BoardDataSource { typealias Item = Session // 显式填空(也可由推断代劳) var allItems: [Session] = [] func title(for item: Session) -> String { item.filmName } } // 泛型函数 + 协议约束:打印任意数据源的看板 func renderBoard<D: BoardDataSource>(_ source: D) { for item in source.allItems { print(source.title(for: item)) } }
关联类型是第7章 SwiftUI 视图的直接前置——View 协议正是用 associatedtype Body 留下"身体是什么视图"的空位。今天把 SessionSource 写顺,明天读 View 协议就不慌。
⚠️ 常见坑一:为了"通用"给约束越叠越多,最后只有两三个类型能上车——先写具体版本,看清真正的共性,再抽泛型。坑二:带关联类型的协议不能直接当变量类型(
var s: BoardDataSource编译报错),要么用泛型参数 D,要么用 any(6.2 节正题)。
💡 关键直觉:抽泛型的时机信号是"复制粘贴第二遍时";约束的选择标准是"机关内真正用到的能力",不多要、不少要。
背景:第一幕到第四幕给场次、影讯各写过一套加载逻辑(2.2 节 BoardStore、5.2 节 BoardLoader),如今新模型"会员权益"又要一套。操作:用泛型 + 约束一次性统一:
struct Loader<Item> { let fetch: () async -> [Item] // 取数逻辑注入(函数类型 2.1 节) private(set) var loaded: [Item] = [] mutating func reload() async { loaded = await fetch() // 逻辑只此一遍 } } // 三个模型共用一台机关 var sessionLoader = Loader(fetch: { (0..<3).map { Session(filmName: "场次\($0)", hall: .standard, minutes: 100 + $0, seatsLeft: 10 - $0) } }) var newsLoader = Loader(fetch: { ["周五悬疑夜", "导演见面会"] }) var rightsLoader = Loader(fetch: { ["生日票半价", "周三会员日"] }) // 调用侧:类型各不相同,代码一模一样 // await sessionLoader.reload() → loaded 为 3 个 Session // await newsLoader.reload() → loaded 为 2 个 String
结果解读:新增第四种模型只需再注入一个取数闭包,加载器本体零改动;取数闭包的类型是函数类型(2.1 节),说明泛型与函数值天然互补——结构复用泛型,行为复用函数。变式:给 Loader 加 where Item: Summarizable2 约束,就能在内部直接生成摘要看板;或把 fetch 换成 (String) async throws -> [Item],把 2.3 节的错误处理一并纳入——async throws 泛型加载器,就是很多开源库的样子。
下一节钻进机关暗层:some 不透明类型、属性包装器与结果构建器——SwiftUI 那套"像声明又像代码"的语法,全靠这三件机关在底下传动。