Swift Enum Loses Initialized Values When Set as a Property

Swift enum with custom initializer loses rawValue initializer

This bug is solved in Xcode 7 and Swift 2

Using an enum to set a property during initialization in swift

You just need to add a new parameter to your initializer.

init(name: String, uniqueGiftedDance: Breakdances) {
super.init(name: name)
self.uniqueGiftedDance = uniqueGiftedDance
Monkeys.monkeysPopulation += 1

}

Can you initialize an Enum value from the name of its case (*not* its RawValue?)

In Swift 4.2, this is quite easy to do now using CaseIterable.

enum MyOtherEnum: CaseIterable {
case xxx
case yyy
case zzz

init?(caseName: String) {
for value in MyOtherEnum.allCases where "\(value)" == caseName {
self = value
return
}

return nil
}
}

enum MyTestEnum: Int, CaseIterable{
case one = 1
case eight = 8
case unknown = -1

init?(caseName: String) {
for value in MyTestEnum.allCases where "\(value)" == caseName {
self = value
return
}

return nil
}
}

What I am doing here is creating a failable initializer which iterates through all potential cases, testing to see if "\(value)" (which returns the name for that potential case) matches the caseName argument passed in to the initializer.

When a match is found, self is set and the loop ends. Otherwise, nil is returned for the call.

Below, are two working and two failing examples:

let myOtherEnum = MyOtherEnum(caseName:"xxx")
print(myOtherEnum) // MyOtherEnum.xxx

let myTestEnum = MyTestEnum(caseName:"eight")
print(myTestEnum?.rawValue) // 8

let myOtherEnumFail = MyOtherEnum(caseName:"aaa")
print(myOtherEnumFail) // nil

let myTestEnumFail = MyTestEnum(caseName:"ten")
print(myTestEnumFail) // nil

Swift enum cannot assign default raw value

In (Objective-)C an enumeration defines a set of named integer constants, and those need not be distinct:

enum {
A = 1,
B = 2,
C = 99,
Default = B
};

The cases of a Swift enum represent mutually distinct values, and their raw values – if assigned – must be unique:

enum BestLetters: Int {
case a = 1
case b
case c
case `default` = 2 // Error: Raw value for enum case is not unique
}

On the other hand, enumerations in Swift are first-class types, so that you can define a static property for that purpose:

enum BestLetters {
case a
case b
case c
static let `default` = BestLetters.b
}

(This is also how Swift imports C enumerations with duplicate values, compare touchIDLockout deprecated in iOS 11.0.)

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

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

Swift Codable - How to Initialize an Optional Enum Property in a Failable Manner

After searching the documentation for the Decoder and Decodable protocols and the concrete JSONDecoder class, I believe there is no way to achieve exactly what I was looking for. The closest is to just implement init(from decoder: Decoder) and perform all the necessary checks and transformations manually.


Additional Thoughts

After giving some thought to the problem, I discovered a few issues with my current design: for starters, mapping a value of 0 in the JSON response to nil doesn't seem right.

Even though the value 0 has a specific meaning of "unspecified" on the API side, by forcing the failable init?(rawValue:) I am essentially conflating all invalid values together. If for some internal error or bug the server returns (say) -7, my code won't be able to detect that and will silently map it to nil, just as if it were the designated 0.

Because of that, I think the right design would be to either:

  1. Abandon optionality for the company property, and define the enum as:

    enum Company: Int {
    case unspecified = 0
    case toyota
    case ford
    case gm
    }

    ...closely matching the JSON, or,

  2. Keep optionality, but have the API return a JSON that lacks a value for the key "company" (so that the stored Swift property retains its initial value of nil) instead of returning 0 (I believe JSON does have a "null" value, but I'm not sure how JSONDecoder deals with it)

The first option requires to modify a lot of code around the whole app (changing occurrences of if let... to comparisons against .unspecified).

The second option requires modifying the server API, which is beyond my control (and would introduce a migration/ backward compatibility issue between server and client versions).

I think will stick with my workaround for now, and perhaps adopt option #1 some time in the future...

Swift optional values during initialization preventing default initializer inheritance

You are on the right track. The issue here is actually the let vs var.

let declares the property constant. In this case Product would have an optional constant name of type String with no initial value, and this of course makes no sense.

The compiler complains about a lacking init() function because let properties are allowed to be set once during init(), as part of object construction, if not defined already in declaration eg.

let name: String = "Im set!" // OK
let name: String? = nil // OK, but very weird :)
let name = "Im set!" // OK, type not needed, implicit.
let name: String // OK, but needs to be set to a string during init()
let name: String? // OK, but needs to be set to string or nil during init()

let name // Not OK

The Swift Programming Language - Constants and Variables



Related Topics



Leave a reply



Submit