Swift enum Basic introduction mobidevtalk.com

Swift enum basic Introductory | mobidevtalk, Learning through case study

SHAD MZUMDER
mobidevtalk
Published in
6 min readDec 24, 2018

--

In Swift enum holds a first class citizen badge. enum provides so many facilities on Swift that you have to book a special place for it. If we yet don’t feel that special affection for enum in Swift, I believe we will sooner or later. This blog series will direct us to fall in a love with enum. 🥰

For a better experience of this blog post we can always visit the original Blog post on mobidevtalk.com.

Background

enum in a 😎 look

enum or Enumerations are not some new concept. Other languages, including Objective-C, always have enum. On Swift, enum is a first class citizen. How? Because on Swift enum can have

  • computed property. stored property is not allowed as enum is value-typed
  • instance method
  • additional initializer
  • extension
  • protocol extension

How enum differs from other collection types?

  • Static elements: all the cases/elements of enum is defined on declaration time, which facilitate low memory footprint and type-safety.
  • Named elements: all the cases/elements of enum are named. Provides the clarity.

For simplicity if we have a collection of known options and at the end we need to choose only one among those options then enum is the preferred building block.

Btw we are intentionally dropping the definition part of enum because it's very obvious.

Basic enum usage concepts

When we will start using enum on a professional level, we will see a lot use of rawValue. So what is rawValue? We will have that answer, but to be more solid we need to find out from where the rawValue comes from. Well it comes from a behavioral type of protocol, RawRepresentable.

enum Fractions: Float{
case whole = 1
case half = 0.5
case quarter = 0.25
}
Fractions.quarter.rawValue * Fractions.half.rawValue //0.125
enum Fractions: String{
case whole
case half
case quarter
}
Fractions.quarter.rawValue //"quarter"

Interesting but what is happening on the background? Well on the background the following happens:

enum Fractions{ 
case whole
case half
case quarter
}
extension Fractions : RawRepresentable{
typealias RawValue = String
var rawValue: String{
switch self {
case .whole:
return "whole"
case .half:
return "half"
case .quarter:
return "quarter"
}
}
init?(rawValue: Fractions.RawValue) {
switch rawValue {
case "whole" :
self = .whole
case "half" :
self = .half
case "quarter" :
self = .quarter
default:
return nil
}
}
}
Fractions.half.rawValue //"half"

[Think we can infer the Int raw typed enum]

Now the obvious question what we need to do when we need a raw type for a enum that is not String Int or Float. Hmm simple and we know the answer. We need to confirms the RawRepresentable protocol for that enum using the custom/desire type as the RawValue. Example please...

So we will use the following struct, Rectangle, as the RawValue of RectFractions. We will implement the rawValue of custom type on RectFractions.

struct Rectangle : Equatable{ 
let width: Float
let height: Float
}

Now why we confirms the Equatable here? Because later down the road we will need the comparison of value equality of Rectangle to construct the enum. Will be a lot clear on some mins.

Following is our enum

enum RectFractions{ 
case whole
case half
case quarter
}

Now let us confirm RawRepresentable for RectFractions.

extension RectFractions: RawRepresentable{ 
typealias RawValue = Rectangle
var rawValue: Rectangle{
switch self {
case .whole:
return Rectangle(width: 1, height: 1)
case .half:
return Rectangle(width: 0.5, height: 0.5)
case .quarter:
return Rectangle(width: 0.25, height: 0.25)
}
}
init?(rawValue: RectFractions.RawValue) {
switch rawValue {
case Rectangle(width: 1, height: 1) :
self = .whole
case Rectangle(width: 0.5, height: 0.5) :
self = .half
case Rectangle(width: 0.75, height: 0.75) :
self = .quarter
default:
return nil
}
}
}

On the very beginning we are declaring Rectangle as the typealias of RawValue. After that we are translating the enum to Rectangle value on var rawValue: Rectangle. And finally we are constructing the enum from Rectangle. Pretty straight forward, right? Now we can do the following:

let halfRect = RectFractions.half.rawValue 
halfRect.width //0.5
halfRect.height //0.5
RectFractions(rawValue: Rectangle(width: 0.75,
height: 0.75))//quarter

associated Values

associated Value basically extends the corresponding case to have an extra value of an enum. Example will ease the concept.

Think about a login state of an app. The login state can have only two case, either logged in or logged out. Now we want to have the user name whenever a user is logged in. On this scenario associated value is the right option. So how we can define an associated value. Simply by defining the type just after the case.

enum LoginState{ 
case loggedOut
case loggedIn(String)
}

Now how to get the user name of the logged in user. Let’s define userName.

extension LoginState{ 
var userName : String?{
switch self {
case .loggedIn(let name): return name
case .loggedOut: return nil
}
}
}

Now the next question is why we are defining the userName as computed property? Why not in the main body of the enum. Well that's a choice, we could have. There is no such strict rules what to do what not to do. The target was separation of concern. userName is not a case of LoginState. And also userName will be calculated after all the cases of LoginState are defined. So it makes no sense to define userName as stored property on the main body.

So finally we can get the user name.

let state = LoginState.loggedIn("mobiDevTalk") 
state.userName //mobiDevTalk

hashValue

Prior to Swift 4.2 hashValue for enum was like 1 ,2 or 3. But now it is not like that and rightfully so. How?

Because hashValue comes from

The very first obvious question is what is hashValue?

hashValue can be think of a unique identifier to identify an element among same typed elements.

A very regular use of hashValue is the git commit hash, which is different from any other commits. A hash will indicate only a single git commit.

enum Suits{ 
case clubs
case diamonds
case hearts
case spades
}
Suits.clubs.hashValue //8039695594957265170

Wait this is not that much smooth. The hash value we are seeing will not be same for the next run 😕. Apple docs says not to save hashValue for future execution.

hashValue itself has a lot of ground to cover. But for enum the hashValue has lost its usage. Previous on the earlier versions of Swift, prior to Swift 4.2, hashValue would act like an Int raw Valued enum. So someEnum.someCase.hashValue would return some Int number like 1, 2 etc. But those days are long gone. It will be an obvious choice not to use hashValue depended operations on enum. Be safe. ⛑

associated Values vs rawValue

enum ErrorCode : String{ 
case https(Int)
//Enum with raw type cannot have cases with arguments case generic
}

What happens here? Why this error.

We are saying, the ErrorCode has a rawValue of String. Thats mean each of the case will have a String type value. But on the case https(Int)we don't have any specific representation of Int type, right? It can be 1, can be 100 who knows! So we can't have an associated value on a raw-typed enum. But wait can we have the other way? What do you think? Do read on...

associated Values & rawValue

We can have an enum with associated value and later on we can confirm the RawRepresentable to set our desire type. Better explained with example

Let us assume we have a enum of department like following:

enum Department: String{ 
case manufacturing
case rnd
case marketing
}
enum Hierarchy{
case chairman
case ceo
case manager(Department)
}

So now we need to have a rawValue of String. For that we need to confirms RawRepresentable and implement the var rawValue and init?(rawValue: RawValue).

extension Hierarchy: RawRepresentable{ 
var rawValue: String{
switch self {
case .chairman: return "Chairman"
case .ceo: return "CEO"
case .manager(let department):
return "\(department.rawValue.capitalized) Manager"
}
}
init?(rawValue: RawValue) {
switch rawValue {
case "Chairman": self = .chairman
case "CEO": self = .ceo
default:
let splitted = rawValue.split(separator: " ")
if let deptVal = splitted.first,
let department = Department(
rawValue:String(deptVal)),
splitted.last == "Manager" {
self = .manager(department)
}else{ return nil }
}
}
}

End, nope not yet

We have talked a lot. But for being a pro on enum can't be gain in a single blog post. So stay tuned. We will be back on our Continue blog post.Till then happy talk. 😌

Source code for this blog post is shared on Github

Originally published at https://mobidevtalk.com on December 24, 2018.

--

--