Review Swift enum types

Overview

This is an article for beginners. Even if you are not a beginner, you may discover something. I will write about enums of Swift grammar.

Operation confirmed with Swift 5.3

First problem

Are Raw Value and Associated Value available at the same time in Swift enums?

If you know this, you may not need to read this article in particular.

enum It is called an enumeration type. Defined by enum and case.

enum CompassPoint {
    case north
    case south
    case east
    case west
}

Store in variable

Variables are ** singular **. If you know which enum type the variable is, you can omit the type like .east.

var directionToHead = CompassPoint.north
directionToHead = .south

var directionToHeadLast: CompassPoint = .east

Switch In the Switch statement, a compile error will occur if all enum cases are not covered. You can also use default.

switch directionToHead {
case .north, .east, .south, .west: print("direction")
}

switch directionToHead {
case .north, .east, .south: print("direction")
default: print("other")
}

Turn the iteration

By adding CaseIterable, you can turn the enum element with a for statement.

It is like this


enum CompassPoint: CaseIterable {
    case north
    case south
    case east
    case west
}


CompassPoint.allCases.forEach{print($0)}

This will output the following:

north
south
east
west

Associated Values

Each element of the enum mentioned earlier can have some value.


enum Appliance {
    case light(isOn: Bool)
    case airCon(degree: Int, isOn: Bool)
}

var appliance: Appliance
appliance = .light(isOn: true)
appliance = .airCon(degree: 28, isOn: true)

When using it in a Switch statement, you can retrieve the Associated Value with let or var.

switch appliance {
case .light(let isOn):
    print(isOn)
case .airCon(let degree, let isOn):
    print(isOn, degree)
}

Note that the Case Iterable that came out earlier cannot be used for enums with ** Associated Values **!

A little technique

When the associated value is all let or all var, it is multiplied as follows.


switch appliance {
case let .light(isOn):
    print(isOn)
case let .airCon(degree, isOn):
    print(isOn, degree)
}

You don't have to write multiple let by putting let in front!

Raw Values

Unlike Associated Values, you can put ** values that are ** fixed at the time of writing in each enum case.

enum Step: Int {
    case one = 1
    case two = 2
    case three = 3
}

print(Step.three.rawValue)

This will display 3.

enum Step: Int {
    case one, two, three
}

print(Step.one.rawValue)

What do you think this will show?

Actually

0

Is displayed.

Only when the Raw Value of Int or String is set, the value is entered without permission. In the case of Int, 0 enters the first case and is incremented one by one. In the case of String, the name of the case itself is entered.

Further research

Let's do more about Raw Value. It will cause problems, so let's guess the output string.

Q1

enum Step: Int,CaseIterable {
    case one, two, three=99
}

Step.allCases.forEach{print($0.rawValue)}
Q1 Answer
0
1
99

Q2


enum Step: Int,CaseIterable {
    case one, two=99, three
}

Step.allCases.forEach{print($0.rawValue)}
Q2 Answer
0
99
100

Q3


enum Step: Int,CaseIterable {
    case one=12, two=11, three
}

Step.allCases.forEach{print($0.rawValue)}
Q3 Answer

There is a compilation error. For three, 12 is entered, which is an increment of 11 for two, but I get angry because this is covered with one.

Q4


enum Step: Int8,CaseIterable {
    case one, two=127, three
}

Step.allCases.forEach{print($0.rawValue)}

Q4 Answer

This is also a compile error Int8 has a max of 127, so I get angry when the three value overflows Int8.

error: MyPlayground.playground:12:24: error: integer literal '128' overflows when stored into 'Int8'
    case one, two=127, three

Initialize with Raw Value

It can be an enum from a rawValue. Note that if you specify a value that is not in the enum, it will be ** nil **!


enum Step: Int, CaseIterable {
    case one=1, two, three
}

Step(rawValue: 2) // two
Step(rawValue: 4) // nil

In other words, the return value will be Optional when initialized with rawValue. Use Optional Bindings when initializing with rawValue

guard let step = Step(rawValue: 4) else {
    fatalError("undefined!!")
}

Are Raw Value and Associated Value available at the same time in Swift enums?

That's the first problem.

The answer is can

If you did it normally, this code


enum CompassPoint: String {
    case north(Int)
    case south(Int)
    case east(Int)
    case west(Int)
}

I get a compile error.

'CompassPoint' declares raw type 'String', but does not conform to RawRepresentable and conformance could not be synthesized

However, in reality, it can be managed.

The way to write the raw value of the enum explained this time is called shorthand notation. It is written as follows.


enum Test: String {
    case pass = "Yay!"
}

The condition to realize RawValue is the compliance with the protocol called RawRepresentable, which is also in the above compilation error. If there is even one Associated Value in the case, shorthand notation will not automatically comply with RawRepresentable. So you can use Raw Value and Associated Value at the same time by doing the following.

enum TestResult {
    case pass(Int)
    case fail(Int)
}

extension TestResult: RawRepresentable {
    typealias RawValue = String
    
    init?(rawValue: RawValue) {
        switch rawValue {
        case "Yay!": self = .pass(1)
        case "Sorry ...": self = .fail(0)
        default:
            return nil
        }
    }
    
    var rawValue: RawValue {
        switch self {
        case .pass: return "Yay!"
        case .fail: return "Sorry ..."
            
        }
    }
}

TestResult.pass(1).rawValue //Yay!
TestResult.fail(0).rawValue //Sorry ...

The caveat is that you have to put the Associated Value in init.

After all, how to write RawValue of shorthand notation is just a shorthand expression, and it seems that the compiler makes it to be RawRepresentable as above. (Reference: The RawRepresentable Protocol in Swift – Ole Begemann)

Recursive enum

If you want to recursively use an enum for an Associated Value, you need to add the indirect keyword to that case.


enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression) //Recursion because the argument here contains itself
}

The indirect can also be brought to the beginning of the enum.

indirect enum ArithmeticExpression: CustomStringConvertible {
    case number(Int)
    case addition(ArithmeticExpression, ArithmeticExpression)
}

Without indirect?

Compile error

error: MyPlayground.playground:2:6: error: recursive enum 'ArithmeticExpression' is not marked 'indirect'
enum ArithmeticExpression: CustomStringConvertible {
     ^
indirect 

I tried to display a character string for a moment

let expression = ArithmeticExpression.addition(.number(10), .addition(.number(1), .number(2)))

print(expression.description)

I want to display the expression as a string in the above print. I tried to display the expression string recursively using CustomStringConvertible.

enum ArithmeticExpression: CustomStringConvertible {
    case number(Int)
    indirect case addition(ArithmeticExpression, ArithmeticExpression)
    
    var description: String {
        get {
            switch self {
            case .number(let num):
                return String(num)
            case .addition(let expr1, let expr2):
                return "\(expr1.description) + \(expr2.description)"
            }
        }
    }
}

Is it like this?

10 + 1 + 2

It was displayed safely!

Summary

reference

Recommended Posts

Review Swift enum types
[Swift] About enumeration types
[Swift] Types of types-Basic knowledge-
[Swift 5] Implement UI for review function
Manage View status with enum in Swift
[Swift / Beginner] Use Enum for maintainable coding