Swift enum avec un initialiseur personnalisé perd l'initialiseur rawValue

95

J'ai essayé de résumer ce problème à sa forme la plus simple avec ce qui suit.

Installer

Version 6.1.1 de Xcode (6A2008a)

Une énumération définie dans 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
        }
    }
}

et le code qui initialise le ENUM dans un autre fichier, 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")
    }
}

Erreur

Xcode me donne l'erreur suivante lors de la tentative d'initialisation MyEnum avec son initialiseur de valeur brute:

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

Remarques

  1. Par le guide du langage Swift :

    Si vous définissez une énumération avec un type à valeur brute, l'énumération reçoit automatiquement un initialiseur qui prend une valeur du type de la valeur brute (en tant que paramètre appelé rawValue) et renvoie un membre d'énumération ounil .

  2. L'initialiseur personnalisé pour a MyEnumété défini dans une extension pour tester si l'initialiseur de valeur brute de l'énumération a été supprimé en raison du cas suivant de Guide du langage . Cependant, il obtient le même résultat d'erreur.

    Notez que si vous définissez un initialiseur personnalisé pour un type valeur, vous n'aurez plus accès à l'initialiseur par défaut (ou à l'initialiseur par membre, s'il s'agit d'une structure) pour ce type. [...]
    Si vous voulez que votre type de valeur personnalisé soit initialisable avec l'initialiseur par défaut et l'initialiseur par membre, ainsi qu'avec vos propres initialiseurs personnalisés, écrivez vos initialiseurs personnalisés dans une extension plutôt que dans le cadre de l'implémentation d'origine du type de valeur.

  3. Le déplacement de la définition d'énumération pour MyClass.swiftrésoudre l'erreur pour barmais pas pour foo.

  4. La suppression de l'initialiseur personnalisé résout les deux erreurs.

  5. Une solution de contournement consiste à inclure la fonction suivante dans la définition d'énumération et à l'utiliser à la place de l'initialiseur de valeur brute fourni. Il semble donc que l'ajout d'un initialiseur personnalisé ait un effet similaire au marquage de l'initialiseur à valeur brute private.

    init?(raw: Int) {
        self.init(rawValue: raw)
    }
  6. La déclaration explicite de la conformité du protocole à RawRepresentabledans MyClass.swiftrésout l'erreur en ligne pour bar, mais entraîne une erreur de l'éditeur de liens concernant les symboles en double (car les énumérations de type à valeur brute sont implicitement conformes à RawRepresentable).

    extension MyEnum: RawRepresentable {}

Quelqu'un peut-il donner un peu plus d'informations sur ce qui se passe ici? Pourquoi l'initialiseur de valeur brute n'est-il pas accessible?

Nickgraef
la source
Vous devriez signaler un bogue à ce sujet - les initialiseurs par défaut devraient avoir une internalportée (ou au moins correspondre au type), non private.
Nate Cook
J'ai exactement le même problème. Une fois que j'ai créé un initialiseur personnalisé, celui par défaut est parti
Yariv Nissim
Ça sent comme un insecte pour moi.
akashivskyy
2
Merci d'avoir validé mes soupçons. Cela a été classé comme un bogue.
nickgraef
Le numéro 5 l'a fait pour moi.
Andrew Duncan

Réponses:

25

Ce bogue est résolu dans Xcode 7 et Swift 2

alcamla
la source
24
Les réponses de ce type bénéficient d'un lien vers le ticket associé afin que les futurs visiteurs puissent vérifier l'état du dossier.
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)
        }
    }
}

Dans votre cas, cela entraînerait l'extension suivante:

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
la source
7

Vous pouvez même rendre le code plus simple et utile sans switchcas, de cette façon, vous n'avez pas besoin d'ajouter plus de cas lorsque vous ajoutez un nouveau type.

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
    }
}
carbonr
la source
1

Ouais, c'est un problème ennuyeux. Je travaille actuellement autour de lui en utilisant une fonction de portée globale qui agit comme une usine, c'est-à-dire

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
    }
}
Cendre
la source
0

Cela fonctionne pour Swift 4 sur Xcode 9.2 avec mon 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)")
    }
}

Production

A for Apple
C for Cat
F for Fun
mclam
la source
-1

Ajoutez ceci à votre code:

extension MyEnum {
    init?(rawValue: Int) {
        switch rawValue {
        case 0: self = .Zero
        case 1: self = .One
        case 2: self = .Two
        default: return nil
        }
    }
}
Tony Swiftguy
la source
Pouvez-vous étendre Int à la place? On dirait que c'est plus facile.
ericgu