Swift Series Four-Enumeration

Moderately aliasing types can make your code easier to understand, more efficient to develop, and more maintainable.

1. typealias (alias)

Typeealias are used to alias types.

typealias Byte = Int8
typealias Short = Int16
typealias Long = Int64

typealias Date = (year: Int, month: Int, day: Int)
func test(_ date: Date) {
    print(date.year)
}
test((2019, 6, 25))
// Output: 2019

typealias IntFn = (Int, Int) -> Int
func diff(v1: Int, v2: Int) -> Int {
    v1 - v2
}
let fn: IntFn = diff
fn(10, 5)
// Output: 5

Void is essentially an alias for an empty ancestor: public typealias Void = ()

2. Enumeration

Swift enumerations are not the same as C/OC languages. Previously, when writing OCs, enumerations were of type int in nature, but enumerations in Swift can be of multiple types.

Official recommendation: Enumeration names are capitalized and members are lowercase.

2.1. Basic Usage

// Define Enumeration
enum YBColor {
    case white
    case black
    case gray
}

// Equivalent to the code above
//enum YBColor {
//    case white, black, gray
//}

var color = YBColor.white
color = YBColor.black
color = .gray // Short form (because it is now determined that the variable color is of type YBColor)
print(color) // Output: gray

// Cycle Control
switch color {
case .white:
    print("white")
case .black:
    print("black")
case .gray:
    print("gray")
}

2.2. Associated Value

Sometimes it is useful to associate enumerated member values with other types of values.

Case:

enum Score {
    case points(Int)
    case grade(Character)
}

// Numerical representation
var score = Score.points(96)
// Level/Character Expression
score = .grade("A")


enum Date {
    case digit(year: Int, month: Int, day: Int)
    case string(String)
}

var date = Date.digit(year: 2019, month: 06, day: 25)
date = .string("2019-06-25")
switch date {
case .digit(let year, let month, let day):
    print(year, month, day, separator:"/")
case let .string(value):
    print(value)
}
/*
 Output:
 2019-06-25
*/

Letters written in front of enumeration members means that enumeration member parameters can only be constants, which allows you to customize the choice between var or let.

2.2. Original Value

Enumeration members can be pre-correlated using the same type of default value called the original value.

enum Direction : Character {
    case up = "w"
    case down = "s"
    case left = "a"
    case right = "d"
}
var direction = Direction.up
print(direction) // Output:up
print(direction.rawValue) // Output:w
print(Direction.down.rawValue) // Output:s

If the original value type of the enumeration is Int, String, Swift automatically assigns the original value:

enum Direction : String {
    case up = "up"
    case down = "down"
    case left = "left"
    case right = "right"
}
var direction = Direction.up
print(direction) // Output:up
print(direction.rawValue) // Output:up
print(Direction.down.rawValue) // Output:down

// equivalence
enum Direction : String {
    case up, down, left, right
}
var direction = Direction.up
print(direction) // Output:up
print(direction.rawValue) // Output:up
print(Direction.down.rawValue) // Output:down

Int type, member value increases by itself (similar to C/OC enumeration):

enum Season : Int {
    case spring, summer, autumn, winter
}
print(Season.spring.rawValue) // Output: 0
print(Season.summer.rawValue) // Output: 1
print(Season.autumn.rawValue) // Output: 2
print(Season.winter.rawValue) // Output: 3

enum Season : Int {
    case spring = 1, summer, autumn = 4, winter
}
print(Season.spring.rawValue)
print(Season.summer.rawValue)
print(Season.autumn.rawValue)
print(Season.winter.rawValue)

2.3. recursive enumeration

  • Keyword: indirect;
  • Members that require recursive enumeration can be preceded by indirect, or directly before the enumeration definition for convenience.
indirect enum ArithExpr {
    case number(Int)
    case sum(ArithExpr, ArithExpr)
    case diff(ArithExpr, ArithExpr)
}

//enum ArithExpr {
//    case number(Int)
//    indirect case sum(ArithExpr, ArithExpr)
//    indirect case diff(ArithExpr, ArithExpr)
//}

let five = ArithExpr.number(5)
let four = ArithExpr.number(4)
let two = ArithExpr.number(2)
let sum = ArithExpr.sum(five, four)
let diff = ArithExpr.diff(sum, two)

func cal(_ expr: ArithExpr) -> Int {
    switch expr {
    case let .number(value):
        return value
    case let .sum(left, right):
        return cal(left) + cal(right)
    case let .diff(left, right):
        return cal(left) - cal(right)
    }
}
cal(diff) // Output: 7

3. Enumerated memory layout

View memory footprint in Swift and use enumeration for alignment: MemoryLayout:

  • size: actual space used
  • stride: Allocate the amount of space occupied
  • Alignment: memory alignment

This means that Int takes up eight bytes of memory, and the number of memory alignments is 8:

MemoryLayout<Int>.size // Output: 8
MemoryLayout<Int>.stride // Output: 8
MemoryLayout<Int>.alignment // Output: 8

View enumeration occupies memory:

enum Password {
    case number(Int, Int, Int, Int)
    case other
}

var pwd = Password.number(1, 2, 2, 3)

MemoryLayout.size(ofValue: pwd) // Output: 33
MemoryLayout.stride(ofValue: pwd) // Output: 40
MemoryLayout.alignment(ofValue: pwd) // Output: 8

Why is the memory size 33 and 40 allocated?

  • number(Int, Int, Int, Int) takes up 32 bytes, other takes up 1 byte, so only 33 bytes is enough in all
  • Since the memory alignment is 8, memory can only be allocated as a multiple of 8, and 33 bytes are less than a multiple of 8, so it's 40 when you fill in the high bits

Why does other take up one byte?

enum Season {
    case spring, summer, autumn, winter
}
MemoryLayout<Season>.size // Output: 1
MemoryLayout<Season>.stride // Output: 1
MemoryLayout<Season>.alignment // Output: 1

// Qualified Type
enum Season: String {
    case spring, summer, autumn, winter
}
MemoryLayout<Season>.size // Output: 1
MemoryLayout<Season>.stride // Output: 1
MemoryLayout<Season>.alignment // Output: 1
  • The code above shows that no matter what type takes up memory size, it is 1 byte.
  • Essentially, it is the difference between the associated value and the original value.

Conclusion 1: The related value passed in is stored directly in the memory of the enumeration variable, so if the enumeration variable is the associated value, the memory must be related to the size of the associated value to be stored.

To confirm conclusion one, compare the following two different types of association values:

enum Password {
    case number(Int, Int, Int, Int)
    case other
}

MemoryLayout<Password>.size // Output: 33
MemoryLayout<Password>.stride // Output: 40
MemoryLayout<Password>.alignment // Output: 8

enum Password {
    case number(String, String, String, String)
    case other
}

MemoryLayout<Password>.size // Output: 65
MemoryLayout<Password>.stride // Output: 72
MemoryLayout<Password>.alignment // Output: 8

Conclusion 2: The original value cannot be modified after it is fixed. Only the corresponding member value (serial number) will be saved in memory. At this time, 1 byte is enough, regardless of the enumeration type (Int or String enumeration takes up one byte).

Analyze the following code:

enum Season: Int {
    // Serial number 0 Serial number 1 Serial number 2 Serial number 3
    case spring = 1, summer = 2, autumn = 3, winter = 4
}

var season1 = Season.spring
var season2 = Season.spring
var season3 = Season.spring
MemoryLayout<Season>.size // Output: 1
MemoryLayout<Season>.stride // Output: 1
MemoryLayout<Season>.alignment // Output: 1

Question: Member values only take up one byte in memory. How does the original value of Int or String survive? rawValue is actually another address.

  • The associated value is stored in the enumeration variable, and the original value does not occupy the memory of the enumeration variable
  • We can see from the memory address that the previous byte is occupied by the associated value, and one byte after the associated value is to save the member value
    • 1 byte stores member values (memory is not consumed if there is only one enumeration member)
    • N bytes store the associated value (N takes the memory-intensive associated value), which is shared by any case's associated value.
    • Complete remaining bytes by alignment

Switch's case actually compares enumerated member values

Keywords: Swift iOS

Added by tazz on Sat, 19 Feb 2022 13:48:18 +0200