How to Simplify Swift Enum Custom Init

How to simplify Swift Enum custom init

You can get rid of the switch statements by defining two dictionaries for a bidirectional mapping between the Int values and the enum cases.

enum Roman: String {
case I, V, X, L, C, D, M

private static let intValues:[Roman:Int] = [.I:1,.V:5,.X:10,.L:50,.C:100,.D:500,.M:1000]
private static let mappingDict:[Int:Roman] = Dictionary(uniqueKeysWithValues: Roman.intValues.map({ ($1, $0) }))

var intValue:Int {
return Roman.intValues[self]!
}

init?(intValue:Int){
guard let roman = Roman.mappingDict[intValue] else { return nil }
self = roman
}
}

Simplifying Swift Enum

What you seem to be doing is currying. You remove a lot of duplicated code by extracting a curry function:

func curry<A,B,C>(_ f: @escaping (A, B) -> C) -> (A) -> (B) -> C {
return { a in { b in f(a, b) } }
}

// ...

var op: (Double) -> (Double) -> Double {
switch self {
case .plus: // please follow Swift naming conventions, enum cases start with a lowercase
return curry(+)
case .minus:
return curry(-)
case .multiply:
return curry(*)
case .unsafeDivide:
return curry(/)
}
}

That already looks a lot nicer. You seem to not like switch statements, so here's how you'd do it with a dictionary:

var op: (Double) -> (Double) -> Double {
let dict: [Operate: (Double, Double) -> Double] =
[.plus: (+), .minus: (-), .multiply: (*), .unsafeDivide: (/)]
return curry(dict[self]!)
}

In fact, you can use the new callAsFunction feature in Swift 5.2 to omit even the word op on the caller side:

func callAsFunction(_ a: Double) -> (Double) -> Double {
op(a)
}

This allows you to do:

Operator.multiply(2)(3)

Using associated values is another way:

enum Operate {
case plus(Double)
case minus(Double)
case multiply(Double)
case unsafeDivide(Double)

func callAsFunction(_ b: Double) -> Double {
switch self {
case .plus(let a):
return a + b
case .minus(let a):
return a - b
case .multiply(let a):
return a * b
case .unsafeDivide(let a):
return a / b
}
}
}

But I personally don't like it because having associated values means that you can't simply use == to compare enum values, among other restrictions.


Preventing dividing by 0 at compile time is impossible, because the values you pass in might not be compile time constants. If you just want to check for compile time constants, then you might need a static code analyser like SwiftLint. At runtime, division of the Double 0 is well-defined by the IEEE standard anyway. It won't crash or anything.

How to declare and init nested enum with reserved keyword as type name in Swift?

There is no way to do this specific thing you want to do. That's why nobody uses nested types named Type, even though we all want to—the language already provides this type, and you don't get to override it with your own. We all use the Objective-C style naming of just smashing the word Type right up there without a proper delimiter.

FooType is what you've got to work with.

How to iterate through a enum to create a custom picker?

You're trying to use ForEach on a single ActivityLevelSelector item. For ForEach to work, it needs to be a collection of items (an Array, for example). This might be more what you're looking for:

struct ContentView: View {
let activityLevels: [ActivityLevelSelector] = ActivityLevelSelector.allCases

var body: some View {
NavigationView {
VStack {
Text("Please select your activity level.")
.font(.caption)
LazyHGrid(rows: [GridItem(.fixed(2))]) {
ForEach(activityLevels, id: \.self) { levelSelection in
Text(levelSelection.description)
}
}
}
}
}
}

If .allCases is indeed what you want, you could simplify even a bit more, getting rid of the let activityLevels and doing::

ForEach(ActivityLevelSelector.allCases, id: \.self) { levelSelection in

Note also that you can't just print inside the ForEach -- you need to return a View of some sort.

Int enums with String representation

I put this together from the linked answers which supports all conversions.

enum Fruits : Int, CaseIterable {
case banana = 1
case apple = 123

init?<S: StringProtocol>(_ string: S) {
guard let value = Fruits.allCases.first(where: { "\($0)" == string }) else {
return nil
}
self = value
}
var stringRepresentation: String {
return "\(self)"
}
}

let favorite = Fruits(rawValue: 123)!
let apple = Fruits("apple")!
assert(favorite == apple)
assert(favorite.rawValue == 123)
assert(String(describing: favorite) == "apple")

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...

How to simplify almost equal enum extensions in Swift

The essence of your question is that you want to avoid writing boilerplate code for all of these enumerations when implementing Argo's JSONDecodable. It looks like you've also added a create method, which is not part of the type signature of JSONDecodable:

public protocol JSONDecodable {
typealias DecodedType = Self
class func decode(JSONValue) -> DecodedType?
}

Unfortunately this cannot be done. Swift protocols are not mixins. With the exception of operators, they cannot contain any code. (I really hope this gets "fixed" in a future update of Swift. Overridable default implementations for protocols would be amazing.)

You can of course simplify your implementation in a couple of ways:

  1. As Tony DiPasquale suggested, get rid of .unknown and use optionals. (Also, you should have called this .Unknown. The convention for Swift enumeration values is to start them with a capital letter. Proof? Look at every enumeration Apple has done. I can't find a single example where they start with a lower case letter.)
  2. By using optionals, your create is now just a functional alias for init? and can be implemented very simply.
  3. As Tony suggested, create a global generic function to handle decode. What he did not suggest, though he may have assumed it was implied, was to use this to implement JSONDecodable.decode.
  4. As a meta-suggestion, use Xcode's Code Snippets functionality to create a snippet to do this. Should be very quick.

At the asker's request, here's a quick implementation from a playground. I've never used Argo. In fact, I'd never heard of it until I saw this question. I answered this question simply by applying what I know about Swift to an examination of Argo's source and reasoning it out. This code is copied directly from a playground. It does not use Argo, but it uses a reasonable facsimile of the relevant parts. Ultimately, this question is not about Argo. It is about Swift's type system, and everything in the code below validly answers the question and proves that it is workable:

enum JSONValue {
case JSONString(String)
}

protocol JSONDecodable {
typealias DecodedType = Self
class func decode(JSONValue) -> DecodedType?
}

protocol RawStringInitializable {
init?(rawValue: String)
}

enum StatusValue: String, RawStringInitializable, JSONDecodable {
case Ok = "ok"
case Pending = "pending"
case Error = "error"

static func decode(j: JSONValue) -> StatusValue? {
return decodeJSON(j)
}
}

func decodeJSON<E: RawStringInitializable>(j: JSONValue) -> E? {
// You can replace this with some fancy Argo operators,
// but the effect is the same.
switch j {
case .JSONString(let string): return E(rawValue: string)
default: return nil
}
}

let j = JSONValue.JSONString("ok")
let statusValue = StatusValue.decode(j)

This is not pseudocode. It's copied directly from a working Xcode playground.

If you create the protocol RawStringInitializable and have all your enumerations implement it, you will be golden. Since your enumerations all have associated String raw values, they implicitly implement this interface anyway. You just have to make the declaration. The decodeJSON global function uses this protocol to treat all of your enumerations polymorphically.

Is it possible in Swift 4.2 to write a default extension to an enum with rawValue of a specific type?

You're supplying a concrete type UInt16 within a broader extension i.e. RawRepresentable....etc. So replace UInt16 with RawValue.

Also since the self.init is failable you need your extension initializer to also be failable.

The code below compiles

extension RawRepresentable where RawValue: DataExchangable {
public init?(from data: Data, at pos: inout Int) throws {
let dataExchangeValue = try RawValue(from: data, at: &pos)
self.init(rawValue: dataExchangeValue)
}
}

Singleton with custom init body

You shouldn't declare an enum and a class with the same name, the compiler cannot decide which one you are trying to instantiate, it thinks that static let sharedInstance = AppBundle() here AppBundle refers to the enum.

You should rename your enum to make it have a different name than your class.

There were also some other issues in your code. Namely, you cannot give a default value to appBundle if you want to declare it immutable with the let keyword. I have change the init method to work with the implementation without a default value and declaring appBundle immutable.

class AppBundle {

static let sharedInstance = AppBundle()

enum AppBundleType: String {
case developer
case alpha
case beta
case appStore
}

let appBundle: AppBundleType

private init() {
if let bundleIdentifier = Bundle.main.bundleIdentifier {
switch bundleIdentifier {
case "com.app.developer":
self.appBundle = .developer
case "com.app.beta":
self.appBundle = .beta
case "com.app.alpha":
self.appBundle = .alpha
default:
self.appBundle = .appStore
}
} else {
self.appBundle = .appStore
}
}

}


Related Topics



Leave a reply



Submit