web-dev-qa-db-fra.com

Swift L'énumération avec l'initialiseur personnalisé perd l'initialiseur rawValue

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

Installer

Xcode Version 6.1.1 (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 l'énum 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 de MyEnum avec son initialiseur de valeur brute:

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

Remarques

  1. Per the Swift Guide de la langue :

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

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

    Notez que si vous définissez un initialiseur personnalisé pour un type de valeur, vous n'aurez plus accès à l'initialiseur par défaut (ni à l'initialiseur memberwise, 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 memberwise, ainsi que vos propres initialiseurs personnalisés, écrivez vos initialiseurs personnalisés dans une extension plutôt que dans le cadre de l’implémentation originale du type de valeur.

  3. Déplacement de la définition d’énumération vers MyClass.Swift résout l'erreur pour bar mais 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 enum 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 à celui de marquer l'initialiseur de valeur brute private.

    init?(raw: Int) {
        self.init(rawValue: raw)
    }
    
  6. Déclarant explicitement la conformité du protocole à RawRepresentable dans MyClass.Swift résout l'erreur en ligne pour bar, mais génère une erreur de l'éditeur de liens concernant les symboles en double (car les énumérations de type de valeur brute sont implicitement conformes à RawRepresentable).

    extension MyEnum: RawRepresentable {}
    

Quelqu'un peut-il donner un aperçu de ce qui se passe ici? Pourquoi l'initialiseur de valeur brute n'est-il pas accessible?

88
nickgraef

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

24
alcamla
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
        }
    }
}
13
Antoine

Vous pouvez même rendre le code plus simple et utile sans switch cas, de cette manière, vous n'avez pas besoin d'ajouter d'autres 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
    }
}
6
carbonr

Oui, c'est un problème embêtant. Je travaille actuellement autour de celui-ci en utilisant une fonction globale qui agit comme une usine, c.-à-d.

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
Ash

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

Sortie

A for Apple
C for Cat
F for Fun
0
mclam