Overriding Enum init?(rawValue: String) to not be optional
Asked Answered
D

3

71

I want to have init from rawValue for my enum in Swift to return default value if rawValue init will return nil. Right now I have something like this:

public init(fromRawValue: String){
        self = Language(rawValue: fromRawValue) ?? .English
}

I don't like this because it is completely new initializer. I tried to make something like this:

public init(rawValue: String){
        self = Language(rawValue: fromRawValue) ?? .English
}

But I have got runtime exception with bad access. Can I somehow make it work or I just have to use this new and I cannot override original one to not be optional?

I would like to know if it possible to override original init from rawValue not workaround with completely new one that is using failable one.

Does answered 31/1, 2016 at 21:23 Comment(1)
Possible duplicate of Nonfailable enum initializer with default valueUnmannered
A
161

The default initializer is failable. It means that if the received parameter does not match a valid enum case it does return nil.

Now you want to do 2 incompatibles things:

  1. You want to redefine the default initializer making it not failable. In fact you want a default enum value created when the received param is not valid.
  2. Inside your redefined initializer you want to call a failable initializer (which no longer exists) using the same name of the new one.

This is not possible, I the 3 possible solutions as follows:

  1. Creating a different init

You define a new not failable initializer with a default value, a different parameter name and inside it you call the default failable initializer.

enum Language: String {
    
    case english = "English", italian = "Italian", french = "French"
    
    init(fromRawValue: String) {
        self = Language(rawValue: fromRawValue) ?? .english
    }
}
  1. Redefining the default init

You redefine the default initializer, you make it not failable and you write the full logic inside it.

enum Language: String {
    
    case english = "English", italian = "Italian", french = "French"
    
    init(rawValue: String) {
        switch rawValue {
        case "Italian": self = .italian
        case "French": self = .french
        default: self = .english
        }
    }
}
  1. Creating a static func
enum Language: String {

    case english = "English", italian = "Italian", french = "French"

    static func build(rawValue: String) -> Language {
        return Language(rawValue: rawValue) ?? .english
    }
}

Now you can build a Language value writing:

let italian = Language.build(rawValue: "Italian") // Italian
let defaultValue = Language.build(rawValue: "Wrong input") // English
Amberly answered 1/2, 2016 at 9:39 Comment(2)
Fantastic answer. The "self = ..." was invaluable. All sorts of hidden Swift nuggets in this lang :)Trouveur
You can replace Language in approach #1 's initializer to type(of: self).init for a more generic approach.Amphictyon
D
11

Adding to Luca's solution for redefining the default init, it is possible to additionally make the rawValue parameter type optional, which cuts down on some code at the call site when the data source is not reliable.

enum PrecipitationType: String {
    case rain, snow, sleet, none

    typealias RawValue = String

    init(rawValue: String?) {
        guard let rawValue = rawValue else { self = .none; return }

        switch rawValue {
            case PrecipitationType.rain.rawValue: self = .rain
            case PrecipitationType.snow.rawValue: self = .snow
            case PrecipitationType.sleet.rawValue: self = .sleet
            default: self = .none
        }
    }
}

When I initially tried this it generated multiple errors. The key was to redefine the RawValue typealias to maintain conformance to RawRepresentable.

Disconnect answered 5/8, 2017 at 1:29 Comment(0)
D
1

If you don't want to do a switch and make it more easy to edit I suggest using CaseIterable protocol. Here is an example:

enum Season: String, CaseIterable {
    case spring, summer, fall, winter, unknown

    init?(rawValue: String) {
        self = Self.allCases.first { $0.rawValue == rawValue } ?? .unknown
    }
}

print(Season(rawValue: "autumn"), Season(rawValue: "fall"))
// Prints: `unknown fall`
Deodorant answered 25/1 at 8:53 Comment(1)
Your initializer is still optional (init?). The question asked specifically for a non-optional initializer.Omen

© 2022 - 2024 — McMap. All rights reserved.