Як отримати ім'я значення перерахунку в 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)"

(Я не знайшов способу ввести це значення "за замовчуванням", наприклад, надрукувати "Місто - Мельбурн", не вдаючись до заяви переключення. Використання \(self)при реалізації description/ debugDescriptionвикликає нескінченну рекурсію.)


У коментарях вище String' 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 ("Статус авторизації: (\ статус))".
Jeffro

3
"Станом на Xcode 7 beta 5" безглуздо. Це не Xcode, який визначає будь-що з цього, це компілятор Swift та бібліотеки Swift Runtime Libaries. Я можу використовувати 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"
      }
    }
  }
}

2
Питання Noob, але навіщо ставити 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) }
}

Потім ви можете використовувати його як звичайний виклик методу у вашому екземплярі. Це може також працювати у попередніх версіях 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 enums на сьогодні єдиним способом, наприклад, є розширення перерахунку, 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"

Зауважте, що це не працює, якщо enum використовує @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, і тому вони не працюють як вище.


7

Крім підтримки String (…) (CustomStringConvertible) для переліків у Swift 2.2, є також дещо зламана підтримка відображення для них. Для випадків перерахунків із пов’язаними значеннями можна отримати мітку випадку перерахунку за допомогою відображення:

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) } }
Девід Джеймс

5

Це так невтішно.

У випадку, коли вам потрібні ці імена (що компілятор прекрасно знає точний написання, але відмовляється надати доступ - дякую команді Swift !! -), але не хочете або не можете зробити String основою вашого перерахунку, a багатослівна, громіздка альтернатива полягає в наступному:

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!


Це здається доволі давно звивистим, ніж просто робити повернення String (відображаючи: себе) в описі.
Бун

4

Я наткнувся на це питання і хотів поділитися простим способом створення згаданої магіїФункції

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

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

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

3

Зараз у Swift є те, що відомо як імпліцитно присвоєне сировинне значення . В основному, якщо ви не вводите необов'язкові значення для кожного випадку, а перерахунок має тип String, він виводить, що вихідне значення корпусу є самим у рядковому форматі. Давай спробуйте.

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

let city = City.Melbourne.rawValue

// city is "Melbourne"

3

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

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")
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.