通用可选枚举函数

Posted

技术标签:

【中文标题】通用可选枚举函数【英文标题】:Generic optional enum func 【发布时间】:2018-05-29 13:58:12 【问题描述】:

在我的项目中,我有几个在任何地方都使用的枚举,这些枚举是基于外部 json 创建的,因此输入始终是可选的。如果无法从输入创建枚举,我将定义一个默认值。

我现在做事的例子:

enum TextAlign:String 
    case left, center, right


let rawData = [String:Any]()

func getTextAlign() -> TextAlign 
    if let rawTextAlignString = rawData["textAlign"] as? String, let align = TextAlign(rawValue: rawTextAlignString) 
        return align
    

    return TextAlign.left

let textAlign = self.getTextAlign()

这显然有效,但我想让我的构造函数更快速、更通用并适用于更多这些枚举。我的目标是像这样实例化这些枚举:

let textAlign = TextAlign(rawOptionalValue: rawData["textAlign"] as? String) ?? TextAlign.left

所以我基本上想要一个可失败的初始化程序,我可以为 TextAlign 枚举编写它,但必须有一种方法以更“通用”的方式声明它,以便我可以在我的所有枚举上使用初始化程序:String 实例。 我在 swift 中对泛型的语法和选项有些挣扎。

有什么想法吗?

更新 #1

我看到很多答案都没有错,但可能我在寻找的内容上不够清楚。

我有更多这样的枚举:

enum TextAlign:String 
    case left, center, right


enum CornerRadius:String 
    case none, medium, large


enum Spacing:String 
    case tight, medium, loose

我只想定义 1 个可以初始化所有这些枚举的函数。 (不是因为我懒,而是因为我想了解如何为此使用泛型)

我可能需要的是扩展中的一些静态函数,适用于所有这些“String/RawRepresentable”枚举。我不想为每个枚举编写所有这些失败的初始化程序。 (我相信应该可以,但我不知道语法)

更新 #2

在玩了一下 Joakim 的回答后,我想出了以下解决方案:

extension RawRepresentable 
    static func create<T:Any>(_ value: Any?, defaultValue: Self) -> Self where Self.RawValue == T 
        guard let rawValue = value as? T, let instance = Self.init(rawValue: rawValue) else 
            return defaultValue
        

        return instance
    

这使我可以使用此函数实例化 StringInt(以及更多)类型的枚举。像这样:

enum TextAlign:String 
    case left, center, right


enum CornerRadius:Int 
    case none, medium, large


let json:[String:Any] = [
    "textAlign":"left",
    "cornerRadius":0
]

let cornerRadius = CornerRadius.create(json["cornerRadius"], defaultValue: .medium)
let align = TextAlign.create(json["textAlign"], defaultValue: .center)

我喜欢我可以将 Any? 作为参数传递,并且它通过 let rawValue = value as? T 自行处理转换。

更新#3(解决方案)

好的,这一切的复杂性仍然让我有点困扰,所以我尝试了 init 路由,imo 看起来更干净。整个事情现在看起来像这样:

extension RawRepresentable 
    init(from value: Any?, or defaultValue: Self) 
        self = Self.init(from: value) ?? defaultValue
    

    init?(from value: Any?) 
        guard let rawValue = value as? Self.RawValue, let instance = Self.init(rawValue: rawValue) else 
            return nil
        

        self = instance
    

为了方便起见,我创建了一个带有默认值的可失败和不可失败的 init。

现在我可以像这样实例化任何枚举:

let cornerRadius = CornerRadius(json["cornerRadius"], or: .medium)

// or an optional one
let align = TextAlign(json["textAlign"])

现在我完成了更新...

【问题讨论】:

不清楚。您要求一种通用的方式,但是您的一些 cmets 建议您要像 TextAlign(...) 等那样进行初始化,但这不是通用的。 是的。我要求一种通用的方式,如果可能的话,我想这样初始化。如果不可能,那就不要:) 【参考方案1】:

这是一个可用于从字符串创建枚举项的函数

func createEnumItem<T: RawRepresentable>(_ value: String) -> T? where T.RawValue == String 
    return  T.init(rawValue: value)

然后像这样使用它

let textAlign: TextAlign = createEnumItem("right")!
let radius: CornerRadius = createEnumItem("medium")!

请注意,您始终在变量声明中包含枚举类型。

当然,由于返回值是可选的,因此您需要以比我这里的示例更好的方式处理它。

更新

如果您总是知道这里的默认设置是修改后的版本

func createEnumItem<T: RawRepresentable>(_ value: String, withDefault defaultItem: T) -> T where T.RawValue == String 
    guard let item = T.init(rawValue: value) else 
        return defaultItem
    
    return item

【讨论】:

是的!这就是我一直在寻找的,但是!我更愿意将其称为TextAlign.create(...)CornerRadius.create(...)。应该可以吧? 或者.. 更好,只需使用TextAlign(...)CornerRadius(...)。不确定是否可以实际定义这样的初始化程序。 当然是的,但它不会是通用的,如果你想创建一个像这样的 init 方法,它只是默认方法的简单包装。 感谢您的输入,当我从您的最终解决方案中得出最终解决方案时,我会将您的答案标记为已接受。【参考方案2】:

我认为您不需要为此添加新功能。只需使用Optional.map(_:)

let alignment = rawData["textAlign"].map(TextAlign.init(rawValue:)) ?? .left

【讨论】:

我不喜欢它,但我更喜欢更简洁的初始化程序(请参阅更新 #3)。谢谢。 @GertjanSmits 够公平的。但我不鼓励or: .medium 论点。 Nil 合并是一个简洁且易于理解的特征。我会推荐CornerRadius(json["cornerRadius"]) ?? .medium 而不是CornerRadius(json["cornerRadius"], or: .medium) 我同意,但这确实是一件方便的事情。出于这个原因,我还创建了init?()。我必须为工作选择正确的init,有时可选的我想要的,在其他情况下,我只想在未包装的(默认)枚举上选择switch 没错。 Swift 有很多处理选项的工具。 (!??、条件绑定、模式匹配、Optional.mapOptional.flatMap 等)。围绕这些通用设施制作包装 API 是没有意义的【参考方案3】:

你可以像这样声明你的枚举

enum TextAlign: String 
    case left, center, right

    init(rawOptionalValue: String?) 
        self = TextAlign(rawValue: rawOptionalValue ?? TextAlign.left.rawValue) ?? .left
    

然后像这样实例化它:

let textAlign = TextAlign(rawOptionalValue: rawData["textAlign"] as? String)

更新

下面是一个使用默认值作为可选参数的示例:

enum TextAlign: String 
    case left, center, right

    init(rawOptionalValue: String?, defaultValue: TextAlign = TextAlign.left) 
        self = TextAlign(rawValue: rawOptionalValue ?? defaultValue.rawValue) ?? defaultValue
    


let textAlign1 = TextAlign(rawOptionalValue: "left") // .left
let textAlign2 = TextAlign(rawOptionalValue: "right") // .right
let textAlign3 = TextAlign(rawOptionalValue: "center") // .center
let textAlign4 = TextAlign(rawOptionalValue: "notAnAlignment") // .left
let textAlign5 = TextAlign(rawOptionalValue: nil, defaultValue: .center) // .center

更新 2

好的,现在我明白了。那么根据你的最新更新,我猜你也可以这样做:

extension RawRepresentable 
    init(rawOptionalValue: Any?, defaultValue: Self) 
        guard let value = rawOptionalValue as? Self.RawValue else 
            self = defaultValue
            return
        
        self = Self.init(rawValue: value) ?? defaultValue
    

与我之前的尝试相比,这里唯一的问题是您无法提供默认的defaultValue。所以你会这样使用它:

let textAlign1 = TextAlign(rawOptionalValue: "left", defaultValue: .left) // .left
let textAlign2 = TextAlign(rawOptionalValue: "right", defaultValue: .left) // .right
let textAlign3 = TextAlign(rawOptionalValue: "center", defaultValue: .left) // .center
let textAlign4 = TextAlign(rawOptionalValue: "notAnAlignment", defaultValue: .left) // .left
let textAlign5 = TextAlign(rawOptionalValue: nil, defaultValue: .center) // .center

【讨论】:

当它永远不可能失败时,为什么要让这个初始化成为一个可失败的初始化? 所有都非常有效,但不是我想要的,请在我的问题中查看我的更新。 我明白了。根据您的更新,我添加了另一个“解决方案”:) 哈,几乎一模一样。不错的解决方案 :) 谢谢!【参考方案4】:

你可以试试这个:

 let textAlign = TextAlign(rawValue: rawData["textAlign"] as? String ?? TextAlign.left.rawValue)

【讨论】:

我想到了这一点,并在某个时候得到了它,但它还不够好:)。在这种情况下,来自rawData 的值可以是String,但这并不意味着它是有效的。在您的示例中,let textAlign 仍然是可选的 TextAlign 实例,因为 TextAlign:rawValue 初始化程序是一个可失败的实例。这意味着您必须在行尾附加 ?? TextAlign.left 才能解开它。我认为这让它有点混乱。【参考方案5】:

你可以像这样定义一个静态函数,并在代码中任何你需要的地方使用它

enum TextAlign: String 
    case left, center, right

    static func getTextAlign(rawData: [String:Any]) -> TextAlign 
        if let rawTextAlignString = rawData["textAlign"] as? String, let align = TextAlign(rawValue: rawTextAlignString) 
            return align
        

        return TextAlign.left
    


// Test
var myRawData = [String:Any]()

let textAlign1 = TextAlign.getTextAlign(rawData: myRawData) // left

myRawData["textAlign"] = "center"

let textAlign2 = TextAlign.getTextAlign(rawData: myRawData) // center

【讨论】:

以上是关于通用可选枚举函数的主要内容,如果未能解决你的问题,请参考以下文章

MybatisPlus 学习通用枚举

Python 通用可选参数

linq 等效于通用函数的'select *' sql?

[MyBatisPlus]通用枚举

[MyBatisPlus]通用枚举

[MyBatisPlus]通用枚举