Handling Enums with Raw values
Intro
I have an en enum with associated raw values. I use a very popular and well know enum for representing SegueIdentifiers but it can be any other enum with raw values.
enum SegueIdentifier: String {
case ShowPhoto
case NewPhoto
case Share
}
Swift 2.0 enums get default String values, like that
case ShowPhoto = "ShowPhoto"
Now I want to create an enum instance from the raw String value and perform some actions depending on the enum case.
Create enum instance from raw value
Step 1 - Create an instance of the enum from the raw value
func performSegue(id: String) {
let segue = SegueIdentifier(rawValue: id) //SegueIdentifier?
}
performSegue("ShowPhoto")
performSegue("Delete") //Wrong raw value
The enum with raw values implements RawRepresentable
protocol. You can create it from the raw value and you can get its raw value. The init method is failable, this means that init could fail and return nil. This makes sense because we could pass wrong raw value to the init method.
protocol RawRepresentable {
init?(rawValue: Self.RawValue)
var rawValue: Self.RawValue { get }
}
Step 2 - Handle enum case
Handle Enum value
I need to handle each enum case and perform some corresponding actions for them. There are few ways to do that and I want to show them all here.
1. Unwrap and handle
func performSegue(id: String) {
let segue = SegueIdentifier(rawValue: id)
if let segue = segue {
switch segue {
case .ShowPhoto:
print("Perform ShowPhoto")
case .NewPhoto:
print("Perform NewPhoto")
case .Share:
print("Perform Share")
}
}
}
Because SegueIdentifier(rawValue: id)
init method returns an optional SegueIdentifier first we need to unwrap it. Then if it's not nil we can handle its case.
This code looks ok but there few things I don't like:
- Nested
if let
- It's not Singe Responsible. It unwraps optional value AND handle Enum cases
- It's not the "Golden / Happy Path"" code style. The right side code indent
>>>
, which could eventually become this:
2. Switch on optional Enum
func performSegue(id: String) {
let segue = SegueIdentifier(rawValue: id)
switch segue {
case .Some(.ShowPhoto):
print("Perform ShowPhoto")
case .Some(.NewPhoto):
print("Perform NewPhoto")
case .Some(.Share):
print("Perform Share")
case .None:
print("Wrong raw value")
}
}
We can make a switch on the optional SegueIdentifier?
without unwrapping it. This way we need to handle optional nil
value in the switch and each case statement need to use .Some
to access the value.
We get right on right side code indent but we introduced new Complexity. Now each case include information about optional type handling, the .Some
keyword.
Ideal Solution
What I want:
- Switch should be performed on non-optional
SegueIdentifier
type - Clean "Golden Path" code indent
- A SRP functions that does Only 1 thing
I find the solution N1 to be better and I will use it as a base and improve it. First to solve Single Responsibility problem I will move enum cases handling to separate method
func performSegue(id: String) {
if let segue = SegueIdentifier(rawValue: id) {
handle(segue)
}
}
func handle(segue: SegueIdentifier) {
switch segue {
case .ShowPhoto:
print("Perform ShowPhoto")
case .NewPhoto:
print("Perform NewPhoto")
case .Share:
print("Perform Share")
}
}
Now it looks better but it's not perfect yet. There is still small nested if let
code. Let's get rig of that.
Get rid of if let { ... }
1. Use map
func performSegue(id: String) {
let segue = SegueIdentifier(rawValue: id)
segue.map(handle)
}
Use the Optional map
function func map<U>(@ noescape f: (T) -> U) -> U?
2. if_let function
If you think that a map
should be used only for data transformation and it shouldn't be used for performing actions, you can use if_let
function proposed by Colin Eberhardt.
func if_let<T>(x: T?, fn: (T) -> Void) {
if let x = x {
fn(x)
}
}
func performSegue(id: String) {
let segue = SegueIdentifier(rawValue: id)
if_let(segue, fn: handle)
}
3. Operators
If you like Haskell and Custom operators, you can use Runes or make own operator (Use runes!!!)
infix operator <^> {
associativity left
precedence 130
}
public func <^> <T, U>(@ noescape f: T -> U, a: T?) -> U? {
return a.map(f)
}
func performSegue(id: String) {
let segue = SegueIdentifier(rawValue: id)
handle <^> segue
}
This is it :)
Final notes
- Strive to Single Responsible code: Classes, components, and even functions
- Well know Custom operators are not scary, love them, use them :)
Code Example - Playground