Swift enum із користувацьким ініціалізатором втрачає ініціалізатор rawValue


95

Я намагався звести це питання до найпростішої форми з наступним.

Налаштування

Версія Xcode 6.1.1 (6A2008a)

Перелік, визначений у MyEnum.swift:

internal enum MyEnum: Int {
    case Zero = 0, One, Two
}

extension MyEnum {
    init?(string: String) {
        switch string.lowercaseString {
        case "zero": self = .Zero
        case "one": self = .One
        case "two": self = .Two
        default: return nil
        }
    }
}

і код, який ініціалізує перерахування в іншому файлі MyClass.swift:

internal class MyClass {
    let foo = MyEnum(rawValue: 0)  // Error
    let fooStr = MyEnum(string: "zero")

    func testFunc() {
        let bar = MyEnum(rawValue: 1)  // Error
        let barStr = MyEnum(string: "one")
    }
}

Помилка

Xcode видає мені таку помилку при спробі ініціалізації за MyEnumдопомогою ініціалізатора вихідного значення:

Cannot convert the expression's type '(rawValue: IntegerLiteralConvertible)' to type 'MyEnum?'

Примітки

  1. Відповідно до мовного посібника Swift :

    Якщо ви визначаєте перерахування з типом необробленого значення, перерахування автоматично отримує ініціалізатор, який приймає значення типу вихідного значення (як параметр, що викликається rawValue) і повертає або елемент перерахування, або nil.

  2. Спеціальний ініціалізатор для MyEnumбув визначений у розширенні, щоб перевірити, чи було видалено ініціалізатор необроблених значень перелічення через наступний випадок з Мовного довідника . Однак він досягає того самого результату помилки.

    Зверніть увагу, що якщо ви визначите спеціальний ініціалізатор для типу значення, у вас більше не буде доступу до ініціалізатора за замовчуванням (або ініціалізатора, що входить до складу, якщо це структура) для цього типу. [...]
    Якщо ви хочете, щоб ваш власний тип значення можна було ініціалізувати за допомогою типового ініціалізатора та ініціалізатора по всьому члену, а також за допомогою власних користувацьких ініціалізаторів, напишіть власні ініціалізатори у розширенні, а не як частина оригінальної реалізації типу значення.

  3. Переміщення визначення перерахування MyClass.swiftусуває помилку для, barале не для foo.

  4. Видалення спеціального ініціалізатора усуває обидві помилки.

  5. Одним з обхідних шляхів є включення наступної функції до визначення переліку та використання її замість наданого ініціалізатора вихідних значень. Отже, здається, ніби додавання власного ініціалізатора має подібний ефект до позначення ініціалізатора необробленого значення private.

    init?(raw: Int) {
        self.init(rawValue: raw)
    }
  6. Явне оголошення про відповідність протоколу RawRepresentablein MyClass.swiftусуває вбудовану помилку bar, але призводить до помилки компонувальника щодо повторюваних символів (оскільки перелічені типи необроблених значень неявно відповідають RawRepresentable).

    extension MyEnum: RawRepresentable {}

Хтось може дати трохи більше розуміння того, що тут відбувається? Чому не доступний ініціалізатор необробленого значення?


Вам слід подати помилку щодо цього - ініціалізатори за замовчуванням повинні мати internalобласть (або принаймні відповідати типу), а не private.
Nate Cook

У мене точно така ж проблема. Як тільки я створюю власний ініціалізатор, за замовчуванням його немає
Ярів Ніссім

Мені пахне жучком.
акашівський

2
Дякуємо за підтвердження моїх підозр. Це було зареєстровано як помилку.
nickgraef

Номер 5 зробив це за мене.
Ендрю Дункан,

Відповіді:


25

Ця помилка вирішена в Xcode 7 та Swift 2


24
Такі відповіді отримують прибуток від посилання на відповідний квиток, щоб майбутні відвідувачі могли перевірити стан справи.
Рафаель

14
extension TemplateSlotType {
    init?(rawString: String) {
        // Check if string contains 'carrousel'
        if rawString.rangeOfString("carrousel") != nil {
            self.init(rawValue:"carrousel")
        } else {
            self.init(rawValue:rawString)
        }
    }
}

У вашому випадку це призведе до наступного розширення:

extension MyEnum {
    init?(string: String) {
        switch string.lowercaseString {
        case "zero": 
            self.init(rawValue:0)
        case "one": 
            self.init(rawValue:1)
        case "two":
            self.init(rawValue:2)
        default: 
            return nil
        }
    }
}

7

Ви навіть можете зробити код простішим і кориснішим без switchрегістрів, таким чином вам не потрібно додавати більше регістрів, коли ви додаєте новий тип.

enum VehicleType: Int, CustomStringConvertible {
    case car = 4
    case moped = 2
    case truck = 16
    case unknown = -1

    // MARK: - Helpers

    public var description: String {
        switch self {
        case .car: return "Car"
        case .truck: return "Truck"
        case .moped: return "Moped"
        case .unknown: return "unknown"
        }
    }

    static let all: [VehicleType] = [car, moped, truck]

    init?(rawDescription: String) {
        guard let type = VehicleType.all.first(where: { description == rawDescription })
            else { return nil }
        self = type
    }
}

1

Так, це неприємна проблема. В даний час я працюю над цим, використовуючи функцію глобального масштабу, яка діє як фабрика, тобто

func enumFromString(string:String) -> MyEnum? {
    switch string {
    case "One" : MyEnum(rawValue:1)
    case "Two" : MyEnum(rawValue:2)
    case "Three" : MyEnum(rawValue:3)
    default : return nil
    }
}

0

Це працює для Swift 4 на Xcode 9.2 разом із моєю EnumSequence :

enum Word: Int, EnumSequenceElement, CustomStringConvertible {
    case apple, cat, fun

    var description: String {
        switch self {
        case .apple:
            return "Apple"
        case .cat:
            return "Cat"
        case .fun:
            return "Fun"
        }
    }
}

let Words: [String: Word] = [
    "A": .apple,
    "C": .cat,
    "F": .fun
]

extension Word {
    var letter: String? {
        return Words.first(where: { (_, word) -> Bool in
            word == self
        })?.key
    }

    init?(_ letter: String) {
        if let word = Words[letter] {
            self = word
        } else {
            return nil
        }
    }
}

for word in EnumSequence<Word>() {
    if let letter = word.letter, let lhs = Word(letter), let rhs = Word(letter), lhs == rhs {
        print("\(letter) for \(word)")
    }
}

Вихідні дані

A for Apple
C for Cat
F for Fun

-1

Додайте це до свого коду:

extension MyEnum {
    init?(rawValue: Int) {
        switch rawValue {
        case 0: self = .Zero
        case 1: self = .One
        case 2: self = .Two
        default: return nil
        }
    }
}

Чи можете ви натомість продовжити Int? Здається, це простіше.
ericgu
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.