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

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

Настройка

Xcode Version 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")
    }
}

Error

Xcode дает мне следующую ошибку при попытке инициализировать MyEnum своим инициализатором исходного значения:

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

Примечания

  • В Руководство по быстрому языку:

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

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

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

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

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

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

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

    extension MyEnum: RawRepresentable {}
    

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

Ответ 1

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

Ответ 2

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

Ответ 3

Вы даже можете сделать код более простым и полезным без случаев 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
    }
}

Ответ 4

Да, это раздражающая проблема. Я сейчас работаю над ним, используя функцию глобального масштаба, которая действует как factory, т.е.

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

Ответ 5

Добавьте это в свой код:

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