Szybkie wyliczenie z niestandardowym inicjatorem traci inicjator rawValue


96

Spróbowałem sprowadzić ten problem do jego najprostszej formy w następujący sposób.

Ustawiać

Xcode w wersji 6.1.1 (6A2008a)

Wyliczenie zdefiniowane w 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
        }
    }
}

i kod, który inicjuje wyliczenie w innym pliku 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")
    }
}

Błąd

Xcode wyświetla następujący błąd podczas próby zainicjowania MyEnumprzy użyciu inicjalizatora surowej wartości:

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

Uwagi

  1. Według przewodnika Swift Language :

    Jeśli zdefiniujesz wyliczenie z typem wartości surowej, wyliczenie automatycznie otrzyma inicjator, który przyjmuje wartość typu wartości surowej (jako wywoływany parametr rawValue) i zwraca element członkowski wyliczenia lub nil.

  2. Niestandardowy inicjator dla MyEnumzostał zdefiniowany w rozszerzeniu w celu sprawdzenia, czy inicjator wartości surowej wyliczenia został usunięty z powodu następującego przypadku z przewodnika językowego . Jednak osiąga ten sam wynik błędu.

    Zwróć uwagę, że jeśli zdefiniujesz niestandardowy inicjator dla typu wartości, nie będziesz już mieć dostępu do domyślnego inicjatora (lub inicjatora elementu członkowskiego, jeśli jest to struktura) dla tego typu. [...]
    Jeśli chcesz, aby niestandardowy typ wartości był inicjalizowany za pomocą domyślnego inicjatora i elementu członkowskiego, a także za pomocą własnych niestandardowych inicjatorów, napisz niestandardowe inicjatory w rozszerzeniu, a nie jako część oryginalnej implementacji typu wartości.

  3. Przeniesienie definicji wyliczenia do MyClass.swiftrozwiązania błędu dla, barale nie dlafoo .

  4. Usunięcie niestandardowego inicjatora rozwiązuje oba błędy.

  5. Jednym obejściem jest uwzględnienie następującej funkcji w definicji wyliczenia i użycie jej zamiast podanego inicjatora wartości surowej. Wygląda więc na to, że dodanie niestandardowego inicjatora ma podobny efekt, jak oznaczenie inicjalizatora surowej wartości private.

    init?(raw: Int) {
        self.init(rawValue: raw)
    }
    
  6. Jawne zadeklarowanie zgodności protokołu z RawRepresentablein MyClass.swiftrozwiązuje błąd wbudowany dla bar, ale powoduje błąd konsolidatora dotyczący zduplikowanych symboli (ponieważ wyliczenia typu surowej wartości są niejawnie zgodne z RawRepresentable).

    extension MyEnum: RawRepresentable {}
    

Czy ktoś może podać trochę więcej informacji o tym, co się tutaj dzieje? Dlaczego inicjator wartości nieprzetworzonych nie jest dostępny?


Powinieneś zgłosić błąd w tej sprawie - domyślne inicjatory powinny mieć internalzasięg (lub przynajmniej pasować do typu), a nie private.
Nate Cook

Mam dokładnie ten sam problem. Po utworzeniu niestandardowego inicjatora nie ma domyślnego
Yariv Nissim

Dla mnie pachnie jak robak.
akashivskyy

2
Dziękuję za potwierdzenie moich podejrzeń. Zostało to zgłoszone jako błąd.
nickgraef

Numer 5 zrobił to za mnie.
Andrew Duncan

Odpowiedzi:


26

Ten błąd został rozwiązany w Xcode 7 i Swift 2


25
Tego rodzaju odpowiedzi zyskują na odsyłaczu do powiązanego biletu, aby przyszli goście mogli sprawdzić stan sprawy.
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)
        }
    }
}

W twoim przypadku spowodowałoby to następujące rozszerzenie:

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

Możesz nawet uprościć kod i uczynić go użytecznym bez switchprzypadków, dzięki czemu nie musisz dodawać więcej przypadków, gdy dodajesz nowy typ.

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

Tak, to irytujący problem. Obecnie pracuję nad tym, używając funkcji globalnego zakresu, która działa jak fabryka, tj

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

Działa to dla Swift 4 na Xcode 9.2 razem z moim 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)")
    }
}

Wynik

A for Apple
C for Cat
F for Fun

-1

Dodaj to do swojego kodu:

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

Czy możesz zamiast tego przedłużyć Int? Wydaje się, że to łatwiejsze.
ericgu
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.