Associated Values — Swift 的一种机制,允许将任意数据单独附加到每个枚举变体。与原始值不同(所有情形具有相同类型的值),关联值可以在类型和数量上因情形而异。根据 Apple Documentation, 2026,这是在不创建额外包装类型的情况下精确建模领域的关键能力。
要点
Associated Values — 是枚举情形可以随自身一起存储的附加信息。与原始值不同(原始值在声明级别固定),关联值在创建枚举实例时确定。
想象对支付方式的建模:creditCard 情形可以存储最后 4 位数字和有效期,而 applePay 情形则不存储任何内容。使用关联值,这可以自然地实现,无需子类或可选字段。
根据 Apple 文档,关联值将枚举从简单的常量集合转换为代数数据类型(和类型),这是函数式语言的特征。这显著提高了代码的表现力。
Associated values 在情形后的圆括号中声明,指定参数名称和类型,如同函数一样。
enum MediaFile {
case image(width: Int, height: Int, format: String)
case video(duration: Double, codec: String)
case audio(bitrate: Int)
case unknown
}
创建实例时,关联值作为参数传递:
let photo = MediaFile.image(width: 1920, height: 1080, format: "jpeg")
let clip = MediaFile.video(duration: 120.5, codec: "h264")
Swift 不对关联值的数量或类型施加限制。您可以在关联值内部使用泛型、闭包函数和其他枚举。这使得可以构建复杂的嵌套结构。
关联值的参数名称在创建实例时用作参数标签——这提高了代码的可读性。通过 switch 提取时,名称充当文档,解释每个值的含义。这种方法使代码自文档化,并降低数据匹配时出错的可能性。这在大型 iOS 应用的团队开发中尤其有价值。
Switch — 提取关联值的主要方式。值通过 let 或 var 绑定到常量或变量。
func describe(_ file: MediaFile) -> String {
switch file {
case .image(let w, let h, let fmt):
return "Image \(w)x\(h) .\(fmt)"
case .video(let duration, let codec):
return "Video \(duration)s, \(codec)"
case .audio(let bitrate):
return "Audio \(bitrate)kbps"
case .unknown:
return "Unknown format"
}
}
Swift 支持部分匹配——可以指定关联值的具体值:
switch file {
case .image(let w, let h, "png"):
print("PNG 图像 \(w)x\(h)")
case .image(1920, 1080, _):
print("全高清图像")
default:
break
}
使用通配符(_)可以忽略不需要的关联值,而情形中的常量可以检查与特定值的匹配。
许多初学者将关联值与原始值混淆。它们是具有不同应用场景的不同机制。
| 特性 | 原始值 | 关联值 |
|---|---|---|
| 数据类型 | 所有情形使用同一类型 | 每个情形可不同 |
| 确定时机 | 枚举声明时 | 创建实例时 |
| 必要性 | 所有情形都有值 | 某些情形可以没有值 |
| 兼容性 | 互斥——不能同时使用 | |
根据 Swift Evolution,关联值在 Swift 2.0 中作为枚举重新设计的一部分引入。它们提供了类型的代数方法,其中每个情形都是一个具有自己签名的独立构造器。
关联值在实际的 iOS 项目中积极使用。让我们考虑三种典型场景。
enum APIResponse<T: Codable> {
case success(data: T, cached: Bool)
case failure(error: Error, retryAvailable: Bool)
case loading(progress: Double)
}
enum ViewState<T> {
case idle
case loading(message: String)
case loaded(data: T)
case error(message: String, retryAction: () -> Void)
}
使用关联值,枚举成为有限状态机的强大工具:每个情形——一种状态,关联值——该状态的参数。编译器保证所有转换都被处理。
要在不使用switch的情况下检查一个情形,请使用 if case 或 guard case。当只需要处理一个变体时,这更简洁。
let error = NetworkError.timeout(seconds: 30)
if case .timeout(let seconds) = error {
print("请求在 \(seconds) 秒后超时")
}
guard case .httpError(let code, _) = error else {
return
}
print("HTTP 错误,代码 \(code)")
常见问题
可以,情形可以没有关联值——例如 case unknown。这种情形像普通枚举情形一样行为,没有额外数据。这对于标记状态很方便。
Swift 不会自动为带关联值的枚举生成 Equatable(在 Swift 4.2+ 之前)。添加显式的 Equatable 实现,或使用 switch 逐一比较每个情形。
可以,indirect 枚举中的关联值是构建树和链表的标准技术。情形可以包含带有 indirect 标记的相同类型的关联值。
关联值是类型安全的——每个情形只保证拥有自己的数据。在带有可选项的结构体中,每个实例包含所有字段,并且需要运行时检查 nil。
Swift 不会自动为带关联值的枚举生成 Codable。需要使用自定义编码键通过 switch 对情形进行 encode 和 decode 的手动实现。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。