Swift enum basic Introductory | mobidevtalk, Learning through case study
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
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?
- 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.125enum 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.