web-dev-qa-db-fra.com

Les enums Swift peuvent-ils avoir plusieurs valeurs brutes?

Je souhaite associer deux valeurs brutes à une instance enum (imaginez une enum représentant les types d'erreur, je veux que Error.Teapot ait une propriété de type Int code avec la valeur 418 et une propriété String définie à I'm a teapot.)

Notez la différence entre les valeurs brutes et les valeurs associées ici - je veux que toutes les instances Teapot aient une code de 418, je ne veux pas d'une valeur associée unique pour chaque instance de Teapot.

Existe-t-il un meilleur moyen d'ajouter des propriétés calculées à l'énumération que switched sur self pour rechercher la valeur appropriée?

17
Robert Atkins

Non, une énumération ne peut pas avoir plusieurs valeurs brutes - elle doit être une valeur unique, implémentant le protocole Equatable et convertie littéralement comme décrit dans la documentation .

Je pense que la meilleure approche dans votre cas consiste à utiliser le code d'erreur en tant que valeur brute et une propriété adossée à un dictionnaire statique prérempli avec le code d'erreur en tant que clé et le texte en tant que valeur.

11
Antonio

Non, vous ne pouvez pas avoir plusieurs valeurs brutes associées à une énumération.

Dans votre cas, vous pouvez avoir la valeur brute égale au code et associer une valeur à la description. Mais je pense que l'approche des propriétés calculées est la meilleure option ici.

2
Marcos Crispino

J'ai créé un moyen de simuler cela (pas différent de ce que Marcos Crispino a suggéré dans sa réponse). Loin d’être une solution parfaite mais qui nous permet d’éviter ces cas d’interrupteurs vilains pour chaque propriété différente que nous voulons obtenir.

L'astuce consiste à utiliser une structure en tant que détenteur de "propriétés/données" et à l'utiliser en tant que RawValue dans l'énumération même.

Il y a un peu de duplication mais ça me sert bien jusqu'à présent. Chaque fois que vous souhaitez ajouter un nouveau cas d’énumération, le compilateur vous rappellera de remplir le cas supplémentaire dans le getter rawValue, ce qui devrait vous rappeler de mettre à jour le init?, ce qui vous rappellerait de créer la nouvelle propriété statique sur la structure. 

Essentiel

Code à la Gist:

enum VehicleType : RawRepresentable {

    struct Vehicle : Equatable {
        let name: String
        let wheels: Int

        static func ==(l: Vehicle, r: Vehicle) -> Bool {
            return l.name == r.name && l.wheels == r.wheels
        }

        static var bike: Vehicle {
            return Vehicle(name: "Bicycle", wheels: 2)
        }

        static var car: Vehicle {
            return Vehicle(name: "Automobile", wheels: 4)
        }

        static var bus: Vehicle {
            return Vehicle(name: "Autobus", wheels: 8)
        }
    }

    typealias RawValue = Vehicle

    case car
    case bus
    case bike

    var rawValue: RawValue {
        switch self {
        case .car:
            return Vehicle.car
        case .bike:
            return Vehicle.bike
        case .bus:
            return Vehicle.bus
        }
    }

    init?(rawValue: RawValue) {
        switch rawValue {
        case Vehicle.bike:
            self = .bike
        case Vehicle.car:
            self = .car
        case Vehicle.bus:
            self = .bus
        default: return nil
        }
    }
}

VehicleType.bike.rawValue.name
VehicleType.bike.rawValue.wheels
VehicleType.car.rawValue.wheels

VehicleType(rawValue: .bike)?.rawValue.name => "Bicycle"
VehicleType(rawValue: .bike)?.rawValue.wheels => 2
VehicleType(rawValue: .car)?.rawValue.name => "Automobile"
VehicleType(rawValue: .car)?.rawValue.wheels => 4
VehicleType(rawValue: .bus)?.rawValue.name => "Autobus"
VehicleType(rawValue: .bus)?.rawValue.wheels => 8
2
Nuno Gonçalves

Vous avez plusieurs options. Mais ni l'un ni l'autre ne comporte de valeurs brutes Les valeurs brutes ne sont tout simplement pas le bon outil pour la tâche.

Option 1 (so-so): valeurs associées

Personnellement, je recommande fortement de ne pas avoir plus d'une valeur associée par cas enum. Les valeurs associées doivent être parfaitement évidentes (car elles n'ont pas d'arguments/noms), et en avoir plus d'un fouillis lourdement.

Cela dit, c'est quelque chose que le langage vous permet de faire. Cela vous permet également de définir chaque cas différemment, si vous en aviez besoin. Exemple:

enum ErrorType {
    case teapot(String, Int)
    case skillet(UInt, [CGFloat])
}

Option 2 (meilleure): Tuples! Et propriétés calculées!

Les tuples sont une fonctionnalité intéressante de Swift, car ils vous permettent de créer des types ad-hoc. Cela signifie que vous pouvez le définir en ligne. Sucré!

Si chacun de vos types d'erreur va avoir un code et une description, vous pouvez alors avoir une propriété calculée info (éventuellement avec un meilleur nom?). Voir ci-dessous:

enum ErrorType {
    case teapot
    case skillet

    var info: (code: Int, description: String) {
        switch self {
        case .teapot:
            return (418, "Hear me shout!")
        case .skillet:
            return (326, "I'm big and heavy.")
        }
    }
}

Appeler cela me serait beaucoup plus facile parce que vous pourriez utiliser une syntaxe de points savoureuse:

let errorCode = myErrorType.info.code

1
jakehawken

Une solution de contournement si vous vouliez avoir plusieurs propriétés statiques pour une erreur YourError pourrait être d’importer une liste de propriétés; vous pouvez définir l'objet racine dans un dictionnaire, avec votre valeur brute enum comme clé pour chaque objet, ce qui vous permet de récupérer facilement des données structurées statiques pour l'objet.

C’est un exemple d’importation et d’utilisation d’un plist: http://www.spritekitlessons.com/parsing-a-property-list-using-Swift/

Cela pourrait être excessif pour simplement une description d'erreur, pour laquelle vous pourriez simplement utiliser une fonction statique codée en dur avec une instruction switch pour vos valeurs enum, qui renvoie la chaîne d'erreur dont vous avez besoin. Il suffit de placer la fonction statique dans le même fichier .Swift que votre enum.

Par exemple,

static func codeForError(error : YourErrorType) -> Int {
    switch(error) {
        case .Teapot:
            return "I'm a Teapot"
        case .Teacup:
            return "I'm a Teacup"
        ...
        default:
            return "Unknown Teaware Error"
    }
}

Ceci a l'avantage (comparé à la solution .plist) d'une localisation plus accommodante. Cependant, une liste .plist pourrait simplement contenir une clé utilisée pour récupérer la localisation appropriée, à la place d'une chaîne d'erreur, à cette fin.

1
DivideByZer0

Dans les versions modernes de Swift, il est possible d'obtenir la valeur de chaîne d'un libellé de cas, même si sans cette énumération étant déclarée avec un : String rawValue.

Comment obtenir le nom de la valeur d'énumération dans Swift?

Il n’est donc plus nécessaire de définir et de maintenir une fonction pratique qui bascule sur chaque cas pour renvoyer un littéral de chaîne. De plus, cela fonctionne automatiquement pour toutes les énumérations, même si aucun type de valeur brute n'est spécifié.

Cela vous permet au moins d’avoir "plusieurs valeurs brutes" en ayant à la fois une valeur réelle : Int rawValue ainsi que la chaîne utilisée comme étiquette de casse.

0
pkamb

Cela ne répond pas particulièrement à votre question, qui demandait de trouver un meilleur moyen que switching à travers self de rechercher la valeur appropriée, mais cette réponse peut toujours être utile pour quelqu'un qui cherche dans le futur et qui a besoin d'un moyen simple d'obtenir une chaîne une énumération définie comme un type entier.

enum Error: UInt {
    case Teapot = 418
    case Kettle = 419

    static func errorMessage(code: UInt) -> String {
        guard let error = Error(rawValue: code) else {
            return "Unknown Error Code"
        }

        switch error {
        case .Teapot:
            return "I'm a teapot!"
        case .Kettle:
            return "I'm a kettle!"
        }
    }
}

De cette façon, nous pouvons obtenir le message d'erreur errorMessage de deux manières:

  1. Avec un entier (par exemple, renvoyé sous forme de code d'erreur par un serveur)
  2. Avec une valeur enum (la rawValue nous définissons pour l'énum)

Option 1:

let option1 = Error.errorMessage(code: 418)
print(option1)  //prints "I'm a teapot!"

Option 2:

let option2 = Error.errorMessage(code: Error.Teapot.rawValue)
print(option2)  //prints "I'm a teapot!"    
0
vastopa

Pour commencer, en supposant que vous souhaitiez stocker un code et un message, vous pouvez utiliser une structure pour RawValue

struct ErrorInfo {
    let code: Int
    let message: String
}

L'étape suivante consiste à définir l'énumération comme étant RawRepresentable et à utiliser ErrorInfo comme valeur brute:

enum MyError: RawRepresentable {
    typealias RawValue = ErrorInfo

    case teapot

Il reste à mapper entre les instances de MyError et ErrorInfo:

static private let mappings: [(ErrorInfo, MyError)] = [
        (ErrorInfo(code: 418, message: "I'm a teapot"), .teapot)
    ]

Avec ce qui précède, construisons la définition complète de l'énum:

enum MyError: RawRepresentable {
    static private let mappings: [(ErrorInfo, MyError)] = [
    (ErrorInfo(code: 418, message: "I'm a teapot"), .teapot)
    ]

    case teapot

    init?(rawValue: ErrorInfo) {
        guard let match = MyError.mappings.first(where: { $0.0.code == rawValue.code && $0.0.message == rawValue.message}) else {
            return nil
        }
        self = match.1
    }

    var rawValue: ErrorInfo {
        return MyError.mappings.first(where: { $0.1 == self })!.0
    }
}

Quelques notes:

  • vous pouvez utiliser uniquement le code d'erreur pour la correspondance, mais cela peut entraîner des valeurs brutes incohérentes si les messages diffèrent
  • la quantité de code passe-partout requise pour avoir des valeurs brutes d'un type personnalisé peut ne pas avoir les avantages d'utiliser des valeurs associées.
0
Cristik