如何让String枚举的可失败初始化器返回默认值而非nil?
这个问题我碰到过好多次——编译器自动生成的RawRepresentable可失败初始化器确实没法直接重写,但我们有好几种优雅的方式来实现你要的“未知值返回.other”的需求,不用纠结于重写那个合成的初始化器。
方法1:用静态工厂方法创建安全实例
最简单的方式是给枚举加一个静态方法,内部调用默认的可失败初始化器,失败时返回.other:
enum ServiceValue: String, Decodable { case a, b, c, other // 静态方法处理未知值,返回非可选实例 static func safeValue(from rawValue: String) -> ServiceValue { return ServiceValue(rawValue: rawValue) ?? .other } }
使用的时候就这么写:
// 已知值返回对应case let knownValue = ServiceValue.safeValue(from: "a") // .a // 未知值自动返回.other let unknownValue = ServiceValue.safeValue(from: "somethingUnknown") // .other
这种方式的好处是完全保留了原来的可失败初始化器,同时提供了一个明确的“安全”入口,代码可读性很高。
方法2:自定义非可失败初始化器(注意避免递归)
如果你更习惯用初始化器的方式,可以自定义一个命名不同的非可失败初始化器,同样在内部处理 fallback:
enum ServiceValue: String, Decodable { case a, b, c, other // 自定义安全初始化器,和默认的init?(rawValue:)区分开 init(safeRawValue rawValue: String) { self = ServiceValue(rawValue: rawValue) ?? .other } }
使用示例:
let value = ServiceValue(safeRawValue: "d") // .other
这里要注意别直接命名为init(rawValue:),不然会和编译器生成的可失败初始化器产生冲突,甚至导致递归调用的问题。
方法3:处理Decodable解码场景(重点!)
因为你的枚举还遵守了Decodable,默认的解码逻辑碰到未知值会直接抛出错误。如果要让解码时自动返回.other,我们需要自定义解码初始化器:
enum ServiceValue: String, Decodable { case a, b, c, other init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() let rawValue = try container.decode(String.self) // 尝试用原始值初始化,失败则返回.other self = ServiceValue(rawValue: rawValue) ?? .other } }
这样当你从JSON解码一个未知字符串时,不会抛出错误,而是直接得到.other实例,完美符合你的需求。
为什么不能直接重写合成的可失败初始化器?
顺便解释下你提到的“无法重写”的问题:编译器为RawRepresentable合成的init?(rawValue:)是协议要求的默认实现,它的返回类型是可选型ServiceValue?。如果我们尝试写一个非可失败的init(rawValue:),这属于方法重载而非重写,但会和合成的初始化器签名冲突(因为Swift会把可失败和非可失败初始化器视为不同的方法,但命名相同的话容易造成调用歧义),所以官方并不推荐这么做,反而用上面的自定义方法更清晰。
内容的提问来源于stack exchange,提问作者Mark A. Donohoe

