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:
- 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.
- 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:
- 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
}
}
- 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
}
}
}
- 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:
Abandon optionality for the
company
property, and define theenum
as:enum Company: Int {
case unspecified = 0
case toyota
case ford
case gm
}...closely matching the JSON, or,
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 returning0
(I believe JSON does have a "null" value, but I'm not sure howJSONDecoder
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
Parse.Com Querying User Class (Swift)
Uicollectionviewlayout Not Working with Uiimage in Swift 5 and Xcode 11
How to Load Image in Swift Using Alamofire
How to Display Realm Results in Swiftui List
Swiftui - Two Buttons in a List
Swift 3: Atomic_Compare_Exchange_Strong
Working with C Strings in Swift, Or: How to Convert Unsafepointer<Cchar> to Cstring
Swift: Second Occurrence with Indexof
Swift - Cast Int64 to Anyobject for Nsmutablearray
Compare Textfield.Text to Firebase String Swift
How to Rotate Only One View Controller to Landscape Orientation in iOS Swift 3
Nstextfield, Change Text in Swift
How to Change Font Size and Font Name of Uisegmentedcontrol Programmatically on Swift