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
. 🥰
This blog post covers the basic part of enum on Swift. For a more in-depth knowledge on enum we can visit the enum hub page, where we listed all the blog post related with Swift enum.
Background
enum
as a first class citizen onSwift
- When to choose
enum
over other collection types - rawValue
- rawValue of custom type
- associated Value
- hashValue
associatedValue
&rawValue
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?
When we talk about enum
, most of the time we think enum
as a group of related values. But wait, array and set also does the grouping. So which concepts make us choose enum
over 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 intensionally dropping the definition part of enum
because it’s very obvious.
Basic enum
usage concepts
To use enum
at its full potential, the very first thing we need to do is to have some very clear understanding of three concepts. Among those three, rawValue
and hashValue
are behavioral type concept. On the other hand associateValue
is a storing type concept.
rawValue
of RawRepresentable
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 behaviour type of protocol, RawRepresentable
.
Swift compiler will automatically provide the RawRepresentable
conformance for the enum, if that enum has the raw type ofInt
, String
or Float
.
enum Fractions: Float{
case whole = 1
case half = 0.5
case quarter = 0.25
}
Fractions.quarter.rawValue * Fractions.half.rawValue //0.125
For String
raw typed enum
if no value is set for the cases then the rawValue
will be the name of that case.
enum Fractions: String{
case whole
case half
case quarter
}
Fractions.quarter.rawValue //"quarter"
Interesting but what is happing 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"
For the Int
, Float
and String
swift compiler can make the conformance of RawRepresentable
for that enum.
[Think we can infer the Int
raw typed enum
]
rawValue
of custom type
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 lets 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)
}
As we can see the loggedIn
case has an associated value of String
type. We can also name the associated value such as, case loggedIn(userName: 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
}
}
}
As we can see we are saving the associated value of loggedIn
case on a variable by defining name
, let name
. we can alternatively do as following, case let .loggedIn(name):
. Both are identical, it’s just a declaration.
Now the next question is why we are defining the userName
as computed property? Why not in the main body of the enum
. Well thats a choice, we cloud 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.
When we define an enum
without any associated values, that enum
gains the Hashable
protocol conformance automatically.
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
Well let have a simple question, did we ever saw an enum
with both rawValue
and associated value? If we try to write something like above we will get the raw type cannot have cases with arguments
error.
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
}
Then we have another enum
of Hierarchy
. The special case is the manager
who can have any of the Department
as the associated value.
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
}
}
}
}
As we can see rawValue
is a computed property. We are defining the value based on the Hierarchy
. On the other hand init?(rawValue: RawValue)
is an optional initialiser for Hierarchy
. Think the logic are pretty clear.
note: typealias RawValue = String
will not be needed once we define the var rawValue
and init?(rawValue: RawValue)
.
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
3 thoughts on “Love for Enum”