在 Swift 中为枚举提供默认值的最佳方法是什么?

What is the best approach to provide default value for enum in Swift?

所以我有几个枚举如下所示:

enum TrackType: String, CustomStringConvertible {
    case video
    case audio
    case subtitles
    case unsupported
        
    var description: String {
        switch self {
            case .video: return "视频"
            case .audio: return "音轨"
            case .subtitles: return "字幕"
            case .unsupported: return "不支持的 track"
        }
    }
        
    // custom initializater to provide default value
    // so I don't have to write:
    // "TrackType.init(rawValue: value) ?? .unsupported" 
    // everywhere
    init(rawValue: String) {
        switch rawValue {
            case "video": self = .video
            case "audio": self = .audio
            case "subtitles": self = .subtitles
            default: self = .unsupported
        }
    }
}

// usage
let value = "foobar"
let trackType = TrackType.init(rawValue: value) // .unsupported

这种方法的缺点是我必须手动列出我编写的每个枚举的所有情况,所以我是这样的:

extension TrackType: ExpressibleByStringLiteral {
    init(stringLiteral value: String) {
        guard let validValue = Self(rawValue: value) else {
            self = .unsupported
            return
        }
        self = validValue
    }
}

// usage
let value = "foobar"
let trackType = value as TrackType // .unsupported

这样我可以避免繁琐的列表工作,但是我所有的枚举都必须符合 ExpressibleByStringLiteral,所以它仍然是重复的

我试着制定这样的协议:

protocol StringEnum: ExpressibleByStringLiteral {
    static var `default`: Self { get }

    init?(rawValue: String)
}

extension StringEnum {
    init(stringLiteral value: String) {
        guard let validValue = Self(rawValue: value) else {
            self = Self.`default`
            return
        }
        self = validValue
    }
}

// error:
// Initializer 'init(stringLiteral:)' has different argument labels from those required by protocol 'StringEnum' ('init(rawValue:)')
enum TrackType: StringEnum {
    static var `default` = TrackType.unsupported
    
    case video
    case audio
    case subtitles
    case unsupported
}

我应该从这里去哪里?

我在 Default value for Enum in Swift 中看到了答案,但其中 none 足够方便...

您可以使用 RawRepresentable 枚举的 init(rawValue:) 初始值设定项来修复它。我还将 RawValue 限制为 String-only 枚举。

确实 要求您仍将枚举标记为在 enum 本身中具有 String 原始值。

代码:

protocol StringEnum: RawRepresentable, ExpressibleByStringLiteral where RawValue == String {
    static var `default`: Self { get }
}

extension StringEnum {
    init(stringLiteral value: String) {
        guard let validValue = Self(rawValue: value) else {
            self = Self.`default`
            return
        }

        self = validValue
    }
}

enum TrackType: String, StringEnum {
    static let `default` = TrackType.unsupported

    case video
    case audio
    case subtitles
    case unsupported
}

用法:

let value: TrackType = "foobar"
print(value)

使用“foobar”,结果为“unsupported”。使用“video”,结果为“video”。它工作正常。

您可能已经知道这一点,但以防万一,作为最佳做法,请尽量避免使用字符串文字。人们通常首先使用枚举的原因是您不依赖整个代码中的字符串文字(它们容易出现编译器无法捕获的拼写错误)。

如果您确实需要检查字符串是否是特定枚举中的原始值,则不需要 init。你可以简单地写一下:

TrackType(rawValue: value)

此外,以防万一 - 我不确定这是否适用于您的用例而没有看到其余代码,但如果您想要做的是为某些枚举值提供默认描述,那么你可以做的是:

var description: String
    {
    switch self
        {
        case .video: return "视频"
        case .audio: return "音轨"
        case .subtitles: return "字幕"
        default: return "不支持的 track" // .unsupported or any case other than the 3 above
        }
    }

那么用法是:

let value = "foobar"
let trackType = TrackType(rawValue: value) ?? .unsupported // .unsupported
let description = trackType.description  // "不支持的 track"

我知道你的问题是希望代码尽可能简洁,我认为这已经是你应该做到的简洁了,不会牺牲代码的可读性和理解性。