如何获取Swift枚举的关联值,无论枚举情况如何

21

我有一个名为FormField的对象,它具有两个属性:一个字符串name和一个可以接受任何类型的值value - 因此我将其设置为Any!。然而,在另一个问题中,我被告知使用带有关联值的枚举代替Any!

enum Value {
    case Text(String!)
    case CoreDataObject(NSManagedObject!)
}

class FormField {
    var name: String
    var value: Value?
    // initializers...
}

这种方法检查空值会变得非常冗长。如果我想在表单中显示所有缺失字段的警报视图,我必须在switch语句的每个情况中重复进行nil检查:

这种方式非常啰嗦,以检查是否为空为例。如果我想在表单中显示所有遗漏字段的警告视图,我需要在switch语句的每个case中重复进行nil检查。
for field in self.fields {
    if let value = field.value {
        switch value {
        case .Text(let text):
            if text == nil {
                missingFields.append(field.name)
            }
        case .CoreDataObject(let object):
            if object == nil {
                missingFields.append(field.name)
            }
        }
    }
}

是否有一种更短的方法访问枚举的关联值,而不考虑其类型?如果我将FormField.value设置为Any!,那么上面的代码将非常简单:

for field in self.fields {
    if field.value == nil {
        missingFields.append(field.name)
    }
}

1
你可以在 case 语句中使用 where 子句来删除几行代码:case .Text(let text) where text == nil: - Mike S
@MikeS 很好的提示,谢谢。这样我就可以为每个可能为空的情况添加一个 where 子句并让它继续执行。不过我还是会回到 Any!。将其变成枚举需要我编写过于冗长的代码。 - Matthew Quiros
但这不只是在移动冗长的代码吗?我想你最终还是需要知道那些“Any”的实际类型,然后你将会做一堆类似于“if value is String”、“if value is NSManagedObject”等的判断。 - Mike S
1
另外,在您的 FormField 类中,value 已经是一个可选项。如果只有在有值时才设置 FormFieldvalue,那么您可以直接检查 if field.value == nil。这意味着您也不需要将枚举的关联值隐式解包为可选项。 - Mike S
@MikeS 除非我将相关值设置为可选类型,否则无法将它们与 nil 进行比较。编译器会抱怨关联值的类型不符合 NilLiteralConvertible - Matthew Quiros
显示剩余2条评论
3个回答

11
enum内定义一个方法isMissing() - 只需编写一次即可。 这样你几乎就得到了你想要的:
for field in self.fields {
    if field.value.isMissing() {
        missingFields.append(field.name)
    }
}

它的外观类似于这样(来自Swift解释器):
  1> class Foo {}
   >
  2> enum Value { 
  3.     case One(Foo!) 
  4.     case Two(Foo!) 
  5.      
  6.     func isMissing () -> Bool { 
  7.         switch self { 
  8.         case let .One(foo): return foo == nil 
  9.         case let .Two(foo): return foo == nil 
 10.         } 
 11.     } 
 12. }    
 13> let aVal = Value.One(nil)
aVal: Value = One {
  One = nil
}
 14> aVal.isMissing()
$R0: Bool = true

好主意!也许我会在其他情况下使用它。但现在,对于关联值不为 nil 的情况,如果我有一个包含多个 UITextField 的表单(即,所有这些字段都将保存类型为 .Text(String!) 的值),我无法确定它是哪个 FormField 对象的关联值(仅访问 String! 本身无法告诉我它属于哪个字段)。 - Matthew Quiros
@Cristik 嗯...我想在那个时候我想要一些反射API(例如在这里看到的https://medium.com/@swiftthesorrow/reflection-in-swift-958824116b07) - Klaas

7

在Swift 2中,可以使用反射获取相关值。

为了使其更加方便,只需将以下代码添加到您的项目中,并使用EVAssociated协议扩展枚举即可。

    public protocol EVAssociated {
    }

    public extension EVAssociated {
        public var associated: (label:String, value: Any?) {
            get {
                let mirror = Mirror(reflecting: self)
                if let associated = mirror.children.first {
                    return (associated.label!, associated.value)
                }
                print("WARNING: Enum option of \(self) does not have an associated value")
                return ("\(self)", nil)
            }
        }
    }

然后,您可以使用以下代码访问关联的值:

    class EVReflectionTests: XCTestCase {
            func testEnumAssociatedValues() {
                let parameters:[EVAssociated] = [usersParameters.number(19),
usersParameters.authors_only(false)]
            let y = WordPressRequestConvertible.MeLikes("XX", Dictionary(associated: parameters))
            // Now just extract the label and associated values from this enum
            let label = y.associated.label
            let (token, param) = y.associated.value as! (String, [String:Any]?)

            XCTAssertEqual("MeLikes", label, "The label of the enum should be MeLikes")
            XCTAssertEqual("XX", token, "The token associated value of the enum should be XX")
            XCTAssertEqual(19, param?["number"] as? Int, "The number param associated value of the enum should be 19")
            XCTAssertEqual(false, param?["authors_only"] as? Bool, "The authors_only param associated value of the enum should be false")

            print("\(label) = {token = \(token), params = \(param)")
        }
    }

    // See http://github.com/evermeer/EVWordPressAPI for a full functional usage of associated values
    enum WordPressRequestConvertible: EVAssociated {
        case Users(String, Dictionary<String, Any>?)
        case Suggest(String, Dictionary<String, Any>?)
        case Me(String, Dictionary<String, Any>?)
        case MeLikes(String, Dictionary<String, Any>?)
        case Shortcodes(String, Dictionary<String, Any>?)
    }

    public enum usersParameters: EVAssociated {
        case context(String)
        case http_envelope(Bool)
        case pretty(Bool)
        case meta(String)
        case fields(String)
        case callback(String)
        case number(Int)
        case offset(Int)
        case order(String)
        case order_by(String)
        case authors_only(Bool)
        case type(String)
    }

上述代码现在可以作为cocoapod susbspec使用,网址是https://github.com/evermeer/Stuff#enum。此外,它还有一个枚举扩展,用于枚举所有枚举值。

0
如果所有枚举情况的相关值类型相同,则可以采用以下方法。
enum Value {
    case text(NSString!), two(NSString!), three(NSString!) // This could be any other type including AnyClass
}

// Emulating "fields" datastruct for demo purposes (as if we had struct with properties).
typealias Field = (en: Value, fieldName: String)
let fields: [Field] = [(.text(nil),"f1"), (.two(nil), "f2"), (.three("Hey"), "f3")] // this is analog of "fields"

let arrayOfFieldNamesWithEmptyEnums: [String] = fields.compactMap({
    switch $0.en {
    case let .text(foo), let .two(foo), let .three(foo): if foo == nil { return $0.fieldName } else { return nil }}
})
print("arrayOfFieldNamesWithEmptyEnums \(arrayOfFieldNamesWithEmptyEnums)")

同样地,许多其他的东西也可以被获取。

let arrayOfEnumsWithoutValues: [Value] = fields.compactMap({
    switch $0.en {
    case let .text(foo), let .two(foo), let .three(foo): if foo == nil { return $0.en } else { return nil }}
})
print("arrayOfEnumsWithoutValues \(arrayOfEnumsWithoutValues)")

// just to check ourselves
if let index = arrayOfEnumsWithoutValues.index(where: { if case .two = $0 { return true }; return false }) {
    print(".two found at index \(index)")
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接