在 Swift 中,枚举是一等类型,可以给其添加计算属性,实例方法,构造函数,遵循协议,另外,还可以定义枚举来存储任意类型的关联值。这些特性可以让枚举适用于很多场合。
当我们需要打印日志,如何快速的将枚举值与其关联值一起转化成字符串呢?
当然我们可以让 enum 实现 CustomStringConvertible
等协议,然后实现各个 case 到 string 的转化。当 enum 的数量少时,还可以应付,当数量增加时,总得想想如何简化这个过程吧。
Mirror 的简介
Swift 中的反射机制是通过 Mirror 来实现的。我们可以给一个具体的 subject
(类、枚举、结构体、数字、闭包等所有的 Any 类型)创建一个 Mirror,然后通过 Mirror 来观察这个 subject
。
因此,可以通过 Mirror 获取到枚举的关联值的信息,简化将枚举转成字符串的过程,又不丢失枚举的关联值的信息。
Mirror 最简单的构造器为:
public init(reflecting subject: Any)
Mirror 提供了几个属性来告诉我们想查询的信息:
- displayStyle:类型为
DisplayStyle
enum,说明对象的展示类型,如:Struct、Class、Enum 等。 - children: 类型为
public typealias Child = (label: String?, value: Any)
的数组,其中,label
表示子节点的名称,value
表示子节点的值。 - subjectType:类型为
Any.Type
,说明对象的类型。
利用 Mirror 将枚举快速的转成字符串
假如有如下一个 enum:
enum TestEnum {
case null // 不关联任何值
case string(String) // 关联一个 String
case tuple(String, String) // 关联一个元组 tuple
}
接下里给 TestEnum 添加一个扩展
extension TestEnum {
public var label: String {
// 获取到 enum 值对应的 Mirror
// mirror 的 subjectType 为 TestEnum, displayStyle 为 Optional(Swift.Mirror.DisplayStyle.enum)
let mirror = Mirror(reflecting: self)
// associated 为 enum 值的关联值,如果没有关联值则为空。
// associated 的 label 为关联值的类型, value 为关联值的值
if let associated = mirror.children.first {
// valuesMirror 为枚举值的关联值的 Mirror
let valuesMirror = Mirror(reflecting: associated.value)
if !valuesMirror.children.isEmpty {
// 如果关联值为类、结构体等,children 为其属性
// 如果关联值为数组、字典,children 为其元素
// 将 children 转化为字符串
let parameters = valuesMirror.children.map {
".\($0.value)" }.joined(separator: ",")
return ".\(associated.label ?? "")(\(parameters))"
}
return ".\(associated.label ?? "")(.\(associated.value))"
}
return ".\(self)"
}
}
这样,就可以通过 label
属性获取到枚举值与其关联值转成的字符串:
TestEnum.null.label
TestEnum.string("Hello").label
TestEnum.tuple("Hello", "world").label
参考
- 有关 Mirror 的详细介绍,可以参考 Swift 反射 API 及用法。
label
方法的实现,为本人在看 Hero 源码时学习到的。