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 case
s.
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:
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...
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:
- 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.) - By using optionals, your
create
is now just a functional alias forinit?
and can be implemented very simply. - 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 implementJSONDecodable.decode
. - 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
Swift: Lazily Encapsulating Chains of Map, Filter, Flatmap
Swift and Objectmapper: Nsdate with Min Value
What Is the Markup Format for Documentation on the Parameters of a Block in Swift
How Does Let X Where X.Hassuffix("Pepper") Work
Com.Apple.Itunes Aedeterminepermissiontoautomatetarget Is Always Return -600
Changing Nav Bar Item Programmatically in Swift
How to Test an Optionset with a Switch Statement
Testing Protocol Conformance with Associated Types
Nspopover to Start in a Detached State
Not Getting Expected Delegate Calls When Trying to Restore In-App Purchases with Storekit
Accessing Nested Dictionary from API in Swift
Converting Docx Files to Text in Swift
Appending Text to Nstextview in Swift 3
Syncconfiguration Deprecated, What Is the Proper Use of Syncuser.Configuration()
Cant Change Navigation Bar Height iOS 11
Convert to Latest Swift Syntax' Breaks the Build Even When There Are No Changes