La enumeración rápida con inicializador personalizado pierde el inicializador rawValue

95

He intentado resumir este problema en su forma más simple con lo siguiente.

Preparar

Versión de Xcode 6.1.1 (6A2008a)

Una enumeración definida en 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
        }
    }
}

y el código que inicializa la enumeración en otro archivo, 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 me da el siguiente error al intentar inicializar MyEnumcon su inicializador de valor sin procesar:

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

Notas

  1. Según la guía de idiomas Swift :

    Si define una enumeración con un tipo de valor sin formato, la enumeración recibe automáticamente un inicializador que toma un valor del tipo del valor sin formato (como un parámetro llamado rawValue) y devuelve un miembro de enumeración o nil.

  2. El inicializador personalizado para MyEnumse definió en una extensión para probar si el inicializador de valor sin formato de la enumeración se estaba quitando debido al siguiente caso de la Guía del idioma . Sin embargo, logra el mismo resultado de error.

    Tenga en cuenta que si define un inicializador personalizado para un tipo de valor, ya no tendrá acceso al inicializador predeterminado (o al inicializador por miembros, si es una estructura) para ese tipo. [...]
    Si desea que su tipo de valor personalizado sea inicializable con el inicializador predeterminado y el inicializador por miembros, y también con sus propios inicializadores personalizados, escriba sus inicializadores personalizados en una extensión en lugar de como parte de la implementación original del tipo de valor.

  3. Mover la definición de enumeración a MyClass.swiftresuelve el error para barpero no para foo.

  4. La eliminación del inicializador personalizado resuelve ambos errores.

  5. Una solución es incluir la siguiente función en la definición de enumeración y usarla en lugar del inicializador de valor sin formato proporcionado. Por lo tanto, parece que agregar un inicializador personalizado tiene un efecto similar al de marcar el inicializador de valor sin procesar private.

    init?(raw: Int) {
        self.init(rawValue: raw)
    }
  6. Declarar explícitamente la conformidad del protocolo con RawRepresentablein MyClass.swiftresuelve el error en línea de bar, pero da como resultado un error del enlazador sobre símbolos duplicados (porque las enumeraciones de tipo de valor sin procesar se ajustan implícitamente a RawRepresentable).

    extension MyEnum: RawRepresentable {}

¿Alguien puede proporcionar un poco más de información sobre lo que está sucediendo aquí? ¿Por qué no se puede acceder al inicializador de valor bruto?

Nickgraef
fuente
Debería presentar un error al respecto: los inicializadores predeterminados deben tener internalalcance (o al menos coincidir con el tipo), no private.
Nate Cook
Tengo exactamente el mismo problema. Una vez que creo un inicializador personalizado, el predeterminado desaparece
Yariv Nissim
A mí me huele a bicho.
akashivskyy
2
Gracias por validar mis sospechas. Esto se ha archivado como error.
nickgraef
Número 5 lo hizo por mí.
Andrew Duncan

Respuestas:

25

Este error se resuelve en Xcode 7 y Swift 2

alcamla
fuente
24
Este tipo de respuestas se benefician de un enlace al ticket asociado para que los futuros visitantes puedan comprobar el estado del asunto.
Raphael
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)
        }
    }
}

En su caso, esto resultaría en la siguiente extensión:

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

Incluso puede hacer que el código sea más simple y útil sin switchcasos, de esta manera no necesita agregar más casos cuando agrega un nuevo tipo.

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
    }
}
carbonatador
fuente
1

Sí, este es un problema molesto. Actualmente estoy trabajando en ello usando una función de alcance global que actúa como una fábrica, es decir

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
    }
}
Ceniza
fuente
0

Esto funciona para Swift 4 en Xcode 9.2 junto con mi 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)")
    }
}

Salida

A for Apple
C for Cat
F for Fun
mclam
fuente
-1

Agregue esto a su código:

extension MyEnum {
    init?(rawValue: Int) {
        switch rawValue {
        case 0: self = .Zero
        case 1: self = .One
        case 2: self = .Two
        default: return nil
        }
    }
}
Tony Swiftguy
fuente
¿Puedes extender Int en su lugar? Parece que es más fácil.
ericgu