Как получить имя значения перечисления в Swift?

167

Если у меня есть перечисление с необработанными 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
Евгений
источник

Ответы:

139

По состоянию на Xcode 7 беты 5 (Swift версия 2) теперь можно напечатать имена типов и случаи перечислений по умолчанию с использованием print(_:)или обращенным в Stringиспользовании String«s init(_:)синтаксис инициализации или строки интерполяции. Итак, для вашего примера:

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)"

(Я не нашел способа вызвать это значение «по умолчанию», например, напечатать «Город - Мельбурн», не возвращаясь к инструкции switch. Использование \(self)в реализации description/ debugDescriptionвызывает бесконечную рекурсию.)


Комментарии выше String«S init(_:)& init(reflecting:)инициализаторах описывают именно то , что печатается, в зависимости от того , что отражено Соответствие нормам типа для:

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
44
Важный улов, это работает только для перечислений Swift. Если вы пометите его как @objc, чтобы разрешить поддержку привязки в OS X, это не будет работать.
Клаус Йоргенсен
11
Великий Свифт-специфичный ответ; однако, если вам нужно сделать это для небыстрого перечисления, например, чтобы напечатать значение перечисления (Objective C) CLAuthorizationStatusвнутри вашего locationManager didChangeAuthorizationStatusобратного вызова делегата, вам нужно определить расширение протокола. Например: extension CLAuthorizationStatus: CustomStringConvertable { public var description: String { switch self { case .AuthorizedAlways: return "AuthorizedAlways" <etc> } } }- как только вы это сделаете, все должно работать так, как вы ожидаете: print ("Auth status: (\ status))".
Джеффро
3
«По состоянию на Xcode 7 beta 5» не имеет смысла. Это определяет не XCode, а компилятор Swift и библиотеки времени исполнения Swift. Я могу использовать Xcode 9.3, но мой код все еще может быть Swift 3, и тогда я не смогу использовать функции Swift 4. Используя Xcode 9.3, этот код не работает, несмотря на то, что Xcode 9.3 намного новее, чем Xcode 7.
Mecki
8
Я получил инициализатор 'init (_ :)', требующий, чтобы City соответствовал 'LosslessStringConvertible' на xcode 10.2, Swift 5. Как мы это делаем сейчас?
Rockgecko
73

В настоящее время нет никакого самоанализа по случаям перечисления. Вы должны будете объявить их каждый вручную:

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"
      }
    }
  }
}
drewag
источник
2
Вопрос нуб, но зачем ставить get {return self.rawValue} вместо простого возврата self.value? Я попробовал последний, и он работает просто отлично.
Чак Круцингер,
Вы также можете опустить get { ... }часть для краткости, если не определяете установщик.
iosdude
1
Спасибо за отличный ответ. В Xcode 7.3 я получаю: «Printable был переименован в CustomStringConvertible». Решение простое - в первом примере кода измените первую строку на enum City : String, CustomStringConvertible {. В качестве части протокола CSC вам потребуется изменить свойство на общедоступное , например:public var description : String {
Jeffro
44

В 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) }
}

Затем вы можете использовать его как обычный вызов метода для вашего экземпляра enum. Это может также работать в предыдущих версиях 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.

Матиас Восс
источник
18

Для 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

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модификатор:

https://forums.swift.org/t/why-is-an-enum-returning-enumname-rather-than-caselabel-for-string-describing/27327

Сгенерированные интерфейсы Swift для типов Objective C иногда не включают @objcмодификатор. Эти перечисления, тем не менее, определены в Objective-C и, следовательно, не работают, как описано выше.

pkamb
источник
7

Помимо поддержки перечислений в 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(boo-hoo).

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

Ситуация с отражением должна улучшиться после Swift 3, по-видимому. На данный момент решение String(…), как предлагается в одном из других ответов:

print(String(City.Chelyabinsk)) // prints out Cheylabinsk
mZ2
источник
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) } }
Дэвид Джеймс
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методу, но это дело вкуса. Также обратите внимание, что так называемые staticпеременные должны быть ограничены областью видимости по имени их включающего типа, так как компилятор слишком амнезичен и не может вызвать контекст сам по себе ...

Команде Свифта действительно нужно командовать. Они создали перечисление, которое вы не можете enumerateи то, что вы можете использовать, enumerateявляются «последовательностями», но не enum!

verec
источник
Это выглядит довольно долго, чем просто возвращать строку String (отражая себя) в описании.
Благо
4

Я столкнулся с этим вопросом и хотел поделиться простым способом создания упомянутой волшебной функции.

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

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

let city = City.Melbourne
city.magicFunction() //prints Melbourne
Sev
источник
3

Swift теперь имеет то, что известно как неявно назначаемая необработанная стоимость . В основном, если вы не задаете необработанные значения для каждого случая, и перечисление имеет тип String, это выводит, что необработанное значение дела само является строковым форматом. Давай, попробуй.

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

let city = City.Melbourne.rawValue

// city is "Melbourne"
NSCoder
источник
3

Для Свифта:

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
xevser
источник
1

Просто, но работает ...

enum ViewType : Int {
    case    Title
    case    Buttons
    case    View
}

func printEnumValue(enum: ViewType) {

    switch enum {
    case .Title: println("ViewType.Title")
    case .Buttons: println("ViewType.Buttons")
    case .View: println("ViewType.View")
    }
}
Джимбо Джонс
источник
0

Кажется, самоанализ в Swift Enums работает частично.

Я видел ответ @ drewag и обнаружил, что Enum без rawValues ​​действительно может иметь самоанализ в Swift 5.X с Xcode 11.5. Этот код работает.

public enum Domain: String {
    case network
    case data
    case service
    case sync
    var description: String {
        return "\(self)"     // THIS INTROSPECTION WORKS
    }
}
enum ErrorCode: Int, CustomStringConvertible {
    case success = 200
    case created = 201
    case accepted = 202
    case badRequest = 400
    case unauthorized = 401
    case forbidden = 403
    case notFound = 404
    var code: Int {
        return self.rawValue
    }
    var description: String {
        return "\(self)"      //THIS DOES NOT WORK - EXEC_BAD_ACCESS
    }
}
let errorCode = ErrorCode.notFound
let domain = Domain.network
print(domain.description, errorCode.code, errorCode.description)

Замените "\(self)"на "string"секунду, Enumи вы получите эту распечатку: сеть 404 строки

ПРИМЕЧАНИЕ: Использование String(self)вместо "\(self)" in the first Enum will require the Enum to conform to theLosslessStringConvertible` протокола, а также добавление других инициализаторов, поэтому интерполяция строк представляется хорошим обходным путем.

Чтобы добавить a var description: Stringв перечисление, вы должны будете использовать оператор Switch во всех случаях перечисления, как указано ранее

var description: String {
    switch self {
    case .success: return "Success"
    case .created: return "Created"
    case .accepted: return "Accepted"
    }
}
eharo2
источник