L'enumerazione Swift con l'inizializzatore personalizzato perde l'inizializzatore rawValue


95

Ho cercato di ridurre questo problema alla sua forma più semplice con quanto segue.

Impostare

Versione Xcode 6.1.1 (6A2008a)

Un enum definito 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
        }
    }
}

e il codice che inizializza l'enum in un altro file, 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")
    }
}

Errore

Xcode mi dà il seguente errore quando tento di inizializzare MyEnumcon il suo inizializzatore di valori non elaborati:

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

Appunti

  1. Secondo la Swift Language Guide :

    Se si definisce un'enumerazione con un tipo di valore non elaborato, l'enumerazione riceve automaticamente un inizializzatore che accetta un valore del tipo di valore non elaborato (come un parametro chiamato rawValue) e restituisce un membro di enumerazione o nil.

  2. L'inizializzatore personalizzato per è MyEnumstato definito in un'estensione per verificare se l'inizializzatore del valore non elaborato dell'enumerazione veniva rimosso a causa del seguente caso dalla Guida alla lingua . Tuttavia, ottiene lo stesso risultato di errore.

    Si noti che se si definisce un inizializzatore personalizzato per un tipo di valore, non si avrà più accesso all'inizializzatore predefinito (o all'inizializzatore memberwise, se è una struttura) per quel tipo. [...]
    Se desideri che il tuo tipo di valore personalizzato sia inizializzabile con l'inizializzatore predefinito e l'inizializzatore per i membri, e anche con i tuoi inizializzatori personalizzati, scrivi gli inizializzatori personalizzati in un'estensione piuttosto che come parte dell'implementazione originale del tipo di valore.

  3. Spostando la definizione dell'enumerazione su MyClass.swiftrisolve l'errore per barma non per foo.

  4. La rimozione dell'inizializzatore personalizzato risolve entrambi gli errori.

  5. Una soluzione alternativa consiste nell'includere la seguente funzione nella definizione di enum e utilizzarla al posto dell'inizializzatore di valori non elaborati fornito. Quindi sembra che l'aggiunta di un inizializzatore personalizzato abbia un effetto simile alla marcatura dell'inizializzatore di valori non elaborati private.

    init?(raw: Int) {
        self.init(rawValue: raw)
    }
  6. Dichiarare esplicitamente la conformità del protocollo a RawRepresentablein MyClass.swiftrisolve l'errore inline per bar, ma si traduce in un errore del linker sui simboli duplicati (perché le enumerazioni del tipo di valore grezzo sono implicitamente conformi a RawRepresentable).

    extension MyEnum: RawRepresentable {}

Qualcuno può fornire qualche informazione in più su cosa sta succedendo qui? Perché l'inizializzatore del valore grezzo non è accessibile?


Dovresti segnalare un bug su questo: gli inizializzatori predefiniti dovrebbero avere un internalambito (o almeno corrispondere al tipo), non private.
Nate Cook

Ho esattamente lo stesso problema. Una volta creato un inizializzatore personalizzato, quello predefinito non c'è più
Yariv Nissim

Odora come un insetto per me.
akashivskyy

2
Grazie per aver confermato i miei sospetti. Questo è stato segnalato come bug.
nickgraef

Il numero 5 l'ha fatto per me.
Andrew Duncan

Risposte:


25

Questo bug è stato risolto in Xcode 7 e Swift 2


24
Risposte di questo tipo traggono vantaggio da un collegamento al ticket associato in modo che i futuri visitatori possano verificare lo stato della questione.
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)
        }
    }
}

Nel tuo caso, ciò comporterebbe la seguente estensione:

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

Puoi persino rendere il codice più semplice e utile senza switchcasi, in questo modo non è necessario aggiungere altri casi quando aggiungi un nuovo 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
    }
}

1

Sì, questo è un problema fastidioso. Attualmente ci sto lavorando utilizzando una funzione di ambito globale che funge da factory, ad es

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

Funziona per Swift 4 su Xcode 9.2 insieme al mio 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)")
    }
}

Produzione

A for Apple
C for Cat
F for Fun

-1

Aggiungi questo al tuo codice:

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

Puoi invece estendere Int? Sembra che sia più facile.
ericgu
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.