Schnelle Aufzählung mit benutzerdefiniertem Initialisierer verliert rawValue-Initialisierer


95

Ich habe versucht, dieses Problem mit den folgenden Worten auf die einfachste Form zu bringen.

Konfiguration

Xcode Version 6.1.1 (6A2008a)

Eine Aufzählung definiert in 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
        }
    }
}

und Code, der die Aufzählung in einer anderen Datei initialisiert 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 gibt beim Versuch, MyEnummit seinem Rohwertinitialisierer zu initialisieren, den folgenden Fehler aus :

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

Anmerkungen

  1. Gemäß dem Swift Language Guide :

    Wenn Sie eine Aufzählung mit einem Rohwerttyp definieren, erhält die Aufzählung automatisch einen Initialisierer, der einen Wert des Rohwerttyps (als Parameter mit dem Namen rawValue) verwendet und entweder ein Aufzählungselement oder zurückgibt nil.

  2. Der benutzerdefinierte Initialisierer für MyEnumwurde in einer Erweiterung definiert, um zu testen, ob der Rohwertinitialisierer der Enumeration aufgrund des folgenden Falls aus dem Sprachhandbuch entfernt wurde . Es wird jedoch das gleiche Fehlerergebnis erzielt.

    Beachten Sie, dass Sie, wenn Sie einen benutzerdefinierten Initialisierer für einen Werttyp definieren, keinen Zugriff mehr auf den Standardinitialisierer (oder den Mitgliedsinitialisierer, wenn es sich um eine Struktur handelt) für diesen Typ haben. [...]
    Wenn Sie möchten, dass Ihr benutzerdefinierter Wertetyp mit dem Standardinitialisierer und dem Mitgliedsinitialisierer sowie mit Ihren eigenen benutzerdefinierten Initialisierern initialisiert werden kann, schreiben Sie Ihre benutzerdefinierten Initialisierer in eine Erweiterung und nicht als Teil der ursprünglichen Implementierung des Wertetyps.

  3. Durch Verschieben der Aufzählungsdefinition wird MyClass.swiftder Fehler für, barjedoch nicht für behoben foo.

  4. Durch Entfernen des benutzerdefinierten Initialisierers werden beide Fehler behoben.

  5. Eine Problemumgehung besteht darin, die folgende Funktion in die Aufzählungsdefinition aufzunehmen und anstelle des bereitgestellten Rohwertinitialisierers zu verwenden. Es scheint also, als hätte das Hinzufügen eines benutzerdefinierten Initialisierers einen ähnlichen Effekt wie das Markieren des Rohwertinitialisierers private.

    init?(raw: Int) {
        self.init(rawValue: raw)
    }
  6. Das explizite Deklarieren der Protokollkonformität mit RawRepresentablein MyClass.swiftbehebt den Inline-Fehler für bar, führt jedoch zu einem Linker-Fehler bei doppelten Symbolen (da Aufzählungen vom Typ Rohwert implizit übereinstimmen RawRepresentable).

    extension MyEnum: RawRepresentable {}

Kann jemand etwas mehr Einblick in das geben, was hier vor sich geht? Warum ist der Rohwertinitialisierer nicht verfügbar?


Sie sollten einen Fehler melden - Standardinitialisierer sollten einen internalGültigkeitsbereich haben (oder zumindest dem Typ entsprechen), nicht private.
Nate Cook

Ich habe genau das gleiche Problem. Sobald ich einen benutzerdefinierten Initialisierer erstellt habe, ist der Standard-Initialisierer weg
Yariv Nissim

Riecht für mich nach einem Käfer.
Akashivskyy

2
Vielen Dank, dass Sie meinen Verdacht bestätigt haben. Dies wurde als Fehler abgelegt.
Nickgraef

Nummer 5 hat es für mich getan.
Andrew Duncan

Antworten:


25

Dieser Fehler wurde in Xcode 7 und Swift 2 behoben


24
Antworten dieser Art profitieren von einem Link zum zugehörigen Ticket, damit zukünftige Besucher den Stand der Dinge überprüfen können.
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)
        }
    }
}

In Ihrem Fall würde dies zu folgender Erweiterung führen:

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

Sie können den Code sogar ohne switchFälle einfacher und nützlicher gestalten . Auf diese Weise müssen Sie beim Hinzufügen eines neuen Typs keine weiteren Fälle hinzufügen.

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

Ja, das ist ein nerviges Problem. Ich arbeite derzeit daran, indem ich eine globale Funktion verwende, die als Fabrik fungiert, d. H.

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

0

Dies funktioniert für Swift 4 unter Xcode 9.2 zusammen mit meiner 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)")
    }
}

Ausgabe

A for Apple
C for Cat
F for Fun

-1

Fügen Sie dies Ihrem Code hinzu:

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

Können Sie stattdessen Int erweitern? Scheint einfacher zu sein.
Ericgu
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.