Swift enum с настраиваемым инициализатором теряет инициализатор rawValue

96

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

Настроить

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 Language Guide :

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

  2. Пользовательский инициализатор для MyEnumбыл определен в расширении, чтобы проверить, удалялся ли инициализатор необработанного значения перечисления из-за следующего случая из Language Guide . Однако он дает тот же результат ошибки.

    Обратите внимание: если вы определяете настраиваемый инициализатор для типа значения, у вас больше не будет доступа к инициализатору по умолчанию (или поэлементному инициализатору, если это структура) для этого типа. [...]
    Если вы хотите, чтобы ваш настраиваемый тип значения был инициализирован с помощью инициализатора по умолчанию и поэлементного инициализатора, а также с вашими собственными настраиваемыми инициализаторами, напишите свои настраиваемые инициализаторы в расширении, а не как часть исходной реализации типа значения.

  3. Перемещение определения перечисления для MyClass.swiftустранения ошибки для, barно не для foo.

  4. Удаление настраиваемого инициализатора устраняет обе ошибки.

  5. Один из способов обхода - включить следующую функцию в определение перечисления и использовать ее вместо предоставленного инициализатора необработанного значения. Таким образом, кажется, что добавление настраиваемого инициализатора имеет тот же эффект, что и маркировка инициализатора необработанного значения private.

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

    extension MyEnum: RawRepresentable {}

Может ли кто-нибудь дать немного больше информации о том, что здесь происходит? Почему недоступен инициализатор необработанного значения?

Nickgraef
источник
Вы должны сообщить об этом - инициализаторы по умолчанию должны иметь internalобласть видимости (или, по крайней мере, соответствовать типу), а не private.
Нейт Кук
У меня точно такая же проблема. Как только я создам собственный инициализатор, он исчезнет по умолчанию
Ярив Ниссим
Мне кажется, что это жук.
Акашивский
2
Спасибо за подтверждение моих подозрений. Это было зарегистрировано как ошибка.
nickgraef 05
Номер 5 сделал это за меня.
Эндрю Дункан

Ответы:

26

Эта ошибка решена в Xcode 7 и Swift 2.

Alcamla
источник
25
В ответах такого рода используется ссылка на связанный билет, чтобы будущие посетители могли проверить состояние вопроса.
Рафаэль
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
    }
}
Ясень
источник
1

Это работает для 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