Swift enum basic

Love for Enum



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 in a 😎 look

enum or Enumerations are not some new concept. Other languages, including Objective-C, always have enum. On Swiftenum 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

Leave a Reply

Notifications for mobidevtalk! Cool ;) :( not cool