如何在Swift中获取枚举值的名称?

226

如果我有一份使用原始Integer值的枚举:

enum City: Int {
  case Melbourne = 1, Chelyabinsk, Bursa
}

let city = City.Melbourne

我如何将city的值转换为字符串 Melbourne?语言中是否有此类类型名称内省的功能?

类似这样的操作(以下代码无法运行):

println("Your city is \(city.magicFunction)")
> Your city is Melbourne
12个回答

181
从Xcode 7 beta 5版本开始(Swift 2),您现在可以使用print(_:)默认打印类型名称和枚举情况,或使用Stringinit(_:)初始化器或字符串插值语法转换为String。因此,对于您的示例:
enum City: Int {
    case Melbourne = 1, Chelyabinsk, Bursa
}
let city = City.Melbourne

print(city)
// prints "Melbourne"

let cityName = "\(city)"   // or `let cityName = String(city)`
// cityName contains "Melbourne"

因此不再需要定义和维护一个方便的函数,来切换每个情况并返回字符串常量。此外,这对于任何枚举类型都可以自动工作,即使没有指定原始值类型。
可以使用 debugPrint(_:)& String(reflecting:) 来获取完全限定名称:
debugPrint(city)
// prints "App.City.Melbourne" (or similar, depending on the full scope)

let cityDebugName = String(reflecting: city)
// cityDebugName contains "App.City.Melbourne"

请注意,您可以自定义每种情况下打印的内容:
extension City: CustomStringConvertible {
    var description: String {
        return "City \(rawValue)"
    }
}

print(city)
// prints "City 1"

extension City: CustomDebugStringConvertible {
    var debugDescription: String {
        return "City (rawValue: \(rawValue))"
    }
}

debugPrint(city)
// prints "City (rawValue: 1)"

(I haven't found a way to call into this "default" value, for example, to print "The city is Melbourne" without resorting back to a switch statement. Using \(self) in the implementation of description/debugDescription causes an infinite recursion.)
上述注释指出了String的init(_:)& init(reflecting:)初始化程序打印的内容,具体取决于反映类型的遵循情况。但是,我还没有找到一种方法来调用这个“默认”值,例如打印“城市是墨尔本”,而不必回到switch语句。在实现description/debugDescription时使用\(self)会导致无限递归。
extension String {
    /// Initialize `self` with the textual representation of `instance`.
    ///
    /// * If `T` conforms to `Streamable`, the result is obtained by
    ///   calling `instance.writeTo(s)` on an empty string s.
    /// * Otherwise, if `T` conforms to `CustomStringConvertible`, the
    ///   result is `instance`'s `description`
    /// * Otherwise, if `T` conforms to `CustomDebugStringConvertible`,
    ///   the result is `instance`'s `debugDescription`
    /// * Otherwise, an unspecified result is supplied automatically by
    ///   the Swift standard library.
    ///
    /// - SeeAlso: `String.init<T>(reflecting: T)`
    public init<T>(_ instance: T)

    /// Initialize `self` with a detailed textual representation of
    /// `subject`, suitable for debugging.
    ///
    /// * If `T` conforms to `CustomDebugStringConvertible`, the result
    ///   is `subject`'s `debugDescription`.
    ///
    /// * Otherwise, if `T` conforms to `CustomStringConvertible`, the result
    ///   is `subject`'s `description`.
    ///
    /// * Otherwise, if `T` conforms to `Streamable`, the result is
    ///   obtained by calling `subject.writeTo(s)` on an empty string s.
    ///
    /// * Otherwise, an unspecified result is supplied automatically by
    ///   the Swift standard library.
    ///
    /// - SeeAlso: `String.init<T>(T)`
    public init<T>(reflecting subject: T)
}


查看发布说明了解有关此更改的信息。


8
如果你不想使用 print(enum) 来获取枚举类型的字符串值,你可以使用 String(enum) - Kametrixom
61
重要提示:这仅适用于Swift枚举。如果您将标记设置为@objc以允许在OS X上进行绑定支持,这将不起作用。 - Claus Jørgensen
17
这是一个很好的针对Swift的答案,但是如果你需要在非Swift的枚举中执行此操作,例如在locationManager didChangeAuthorizationStatus委托回调中打印(Objective C)CLAuthorizationStatus枚举的值,那么你需要定义一个协议扩展。例如:extension CLAuthorizationStatus: CustomStringConvertable { public var description: String { switch self { case .AuthorizedAlways: return "AuthorizedAlways" <etc> } } } - 一旦你完成了这个步骤,它应该按照你的预期工作:print("Auth status: (\status))”。 - Goffredo
4
“截至Xcode 7测试版5”这句话没有意义。 它并不是Xcode定义这些内容,而是Swift编译器和Swift运行库。 我可以使用Xcode 9.3,但我的代码仍然可以是Swift 3,那么我就无法使用Swift 4的功能。即使Xcode 9.3比Xcode 7更新得多,但使用Xcode 9.3时,这段代码仍无法工作。 - Mecki
10
在Xcode 10.2和Swift 5中,我得到了这个错误信息:“initializer 'init(_:)' requires that City conform to 'LosslessStringConvertible'”。现在该如何解决呢?请问您有什么建议? - rockgecko
显示剩余10条评论

87

目前枚举案例没有自省。您将不得不手动声明它们:

enum City: String, CustomStringConvertible {
    case Melbourne = "Melbourne"
    case Chelyabinsk = "Chelyabinsk"
    case Bursa = "Bursa"

    var description: String {
        get {
            return self.rawValue
        }
    }
}

如果您需要原始类型为Int,您将不得不自己进行切换:

enum City: Int, CustomStringConvertible {
  case Melbourne = 1, Chelyabinsk, Bursa

  var description: String {
    get {
      switch self {
        case .Melbourne:
          return "Melbourne"
        case .Chelyabinsk:
          return "Chelyabinsk"
        case .Bursa:
          return "Bursa"
      }
    }
  }
}

2
新手问题,但为什么要写成 get { return self.rawValue } 而不是直接写 return self.value?我试过后者,也能正常工作。 - Chuck Krutsinger
2
如果您没有定义 setter,为了简洁起见,您也可以省略 get { ... } 部分。 - iosdude
1
感谢您的出色回答。在Xcode 7.3中,我得到了这样的提示:“Printable已更名为CustomStringConvertible”。解决方案很简单 - 在上面的第一个代码示例中,将第一行更改为enum City:String,CustomStringConvertible {。作为CSC协议的一部分,您还需要将属性更改为public,例如:public var description:String { - Goffredo

64
在Swift-3中(已在Xcode 8.1中测试),您可以在枚举中添加以下方法:
/**
 * The name of the enumeration (as written in case).
 */
var name: String {
    get { return String(describing: self) }
}

/**
 * The full name of the enumeration
 * (the name of the enum plus dot plus the name as written in case).
 */
var description: String {
    get { return String(reflecting: self) }
}
你可以将其作为枚举实例上的普通方法调用。它可能也适用于以前的Swift版本,但我没有测试过。
在你的示例中:
enum City: Int {
    case Melbourne = 1, Chelyabinsk, Bursa
    var name: String {
        get { return String(describing: self) }
    }
    var description: String {
        get { return String(reflecting: self) }
    }
}
let city = City.Melbourne

print(city.name)
// prints "Melbourne"

print(city.description)
// prints "City.Melbourne"

如果你想为所有的枚举提供这个功能,你可以将它作为扩展。

/**
 * Extend all enums with a simple method to derive their names.
 */
extension RawRepresentable where RawValue: Any {
  /**
   * The name of the enumeration (as written in case).
   */
  var name: String {
    get { return String(describing: self) }
  }

  /**
   * The full name of the enumeration
   * (the name of the enum plus dot plus the name as written in case).
   */
  var description: String {
    get { return String(reflecting: self) }
  }
}

这只适用于Swift枚举。


37
String(describing:)初始化程序可用于返回具有非字符串rawValues的枚举的案例标签名称:
enum Numbers: Int {
    case one = 1
    case two = 2
}

let one = String(describing: Numbers.one) // "one"
let two = String(describing: Numbers.two) // "two"

请注意,如果枚举使用@objc修饰符,则不起作用 为Objective-C类型生成的Swift接口有时不包括@objc修饰符。这些枚举在Objective-C中定义,因此不能像上面那样工作。

还在 获取Swift 3中ObjC枚举名称? 中有所提及。 - Alex Cohn

23

对于Objective-C的enum,目前唯一的方法似乎是,例如,使用CustomStringConvertible扩展枚举,最终得到类似以下代码:

对于Objective-C的enum,目前唯一的方法似乎是,例如,使用CustomStringConvertible扩展枚举,最终得到类似以下代码:

extension UIDeviceBatteryState: CustomStringConvertible {
    public var description: String {
        switch self {
        case .Unknown:
            return "Unknown"
        case .Unplugged:
            return "Unplugged"
        case .Charging:
            return "Charging"
        case .Full:
            return "Full"
        }
    }
}

然后将 enum 强制转换为 String:

String(UIDevice.currentDevice().batteryState)

12

除了Swift 2.2中枚举类型支持String(...) (CustomStringConvertible)之外,它们还具有相对瑕疵的反射支持。对于带有关联值的枚举情况,可以使用反射获取枚举情况的标签:

enum City {
    case Melbourne(String)
    case Chelyabinsk
    case Bursa

    var label:String? {
        let mirror = Mirror(reflecting: self)
        return mirror.children.first?.label
    }
}

print(City.Melbourne("Foobar").label) // prints out "Melbourne"

所谓"被破坏",仅仅指对于那些比较"简单"的枚举类型,上述基于反射的label计算属性只会返回nil(唉哟)。

print(City.Chelyabinsk.label) // prints out nil

据说自Swift 3以后,反射技术的情况应该会有所改善。不过目前的解决方法是使用另一个答案中建议的String(…)

print(String(City.Chelyabinsk)) // prints out Cheylabinsk

2
这段代码似乎可以在Swift 3.1上正常工作,而无需将其设置为可选项:var label:String { let mirror = Mirror(reflecting: self); if let label = mirror.children.first?.label { return label } else { return String(describing:self) } } - David James

7

现在,Swift拥有所谓的隐式分配原始值。基本上,如果您没有为每个case指定原始值,并且枚举类型为String,则它会推断出该case的原始值本身是字符串格式。继续尝试一下吧。

enum City: String {
  case Melbourne, Chelyabinsk, Bursa
}

let city = City.Melbourne.rawValue

// city is "Melbourne"

7

我偶然看到了这个问题,并想分享一个简单的方法来创建所提到的magicFunction。

enum City: Int {
  case Melbourne = 1, Chelyabinsk, Bursa

    func magicFunction() -> String {
        return "\(self)"
    }
}

let city = City.Melbourne
city.magicFunction() //prints Melbourne

5

这真是令人失望。

对于那些需要使用编译器已经完全知道拼写的名称(但拒绝访问的情况——谢谢Swift团队!!),但又不想或不能将String作为枚举基础的情况,有一种冗长、繁琐的替代方法如下:

enum ViewType : Int, Printable {

    case    Title
    case    Buttons
    case    View

    static let all = [Title, Buttons, View]
    static let strings = ["Title", "Buttons", "View"]

    func string() -> String {
        return ViewType.strings[self.rawValue]
    }

    var description:String {
        get {
            return string()
        }
    }
}

您可以按以下方式使用上述内容:
let elementType = ViewType.Title
let column = Column.Collections
let row = 0

println("fetching element \(elementType), column: \(column.string()), row: \(row)")

你将得到期望的结果(列的代码类似,但未显示)

fetching element Title, column: Collections, row: 0

在上述代码中,我已将`description`属性指回`string`方法,但这只是个人口味问题。另外请注意,所谓的静态变量需要通过其封闭类型的名称进行作用域限定,因为编译器过于健忘,无法自行记住上下文...
Swift团队确实值得称赞。他们创建了一个枚举,你不能对其进行枚举,而你可以使用`enumerate`在“序列”上进行操作,但不能在枚举上进行操作!

这似乎比在描述中只执行return String(reflecting: self)更冗长。 - Boon

4

对于Swift:

extension UIDeviceBatteryState: CustomStringConvertible {

    public var description: String {
        switch self {
        case .unknown:
            return "unknown"
        case .unplugged:
            return "unplugged"
        case .charging:
            return "charging"
        case .full:
            return "full"
        }
    }

}

如果您的变量"batteryState"存在,则调用以下内容:
self.batteryState.description

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