This article mainly analyzes the associated values and original values of enumeration from memory and assembly

Enumerator value

usage

Enumeration is declared as follows:

enum Direction {
    case East
    case South
    case West
    case North
}
Copy the code

Use:

let direction1 = Direction.east
let direction2: Direction = .east
print(direction1 == direction2)

if direction1 == .east {
  ...
}

switch direction1 {
case .east:
  ...
case .south:
  ...
case .west:
  ...
case .north:
  ...
}
Copy the code

Memory analysis

MemoryLayout provides the enumeration size, memory aligned size, and aligned bytes

print(MemoryLayout<Direction>.size)				/ / 1
print(MemoryLayout<Direction>.stride)			/ / 1
print(MemoryLayout<Direction>.alignment)	/ / 1
Copy the code

Direction requires only 1 byte of the memory store member value, which is used to distinguish between different types. The memory value can be viewed using LLDB or printed using the following method

var d = Direction.east
var ptr = withUnsafePointer(to: d, { UnsafeRawPointer($0)})print(String(format: "0x%02X", ptr.load(as: UInt8.self)))				// 0x00
d = .south
ptr = withUnsafePointer(to: d, { UnsafeRawPointer($0)})print(String(format: "0x%02X", ptr.load(as: UInt8.self)))				// 0x01
d = .west
ptr = withUnsafePointer(to: d, { UnsafeRawPointer($0)})print(String(format: "0x%02X", ptr.load(as: UInt8.self)))				// 0x02
d = .north
ptr = withUnsafePointer(to: d, { UnsafeRawPointer($0)})print(String(format: "0x%02X", ptr.load(as: UInt8.self)))				// 0x03
Copy the code

The Swift enumeration distinguishes types by their member values

Associated Value

usage

In Swift, you can store enumerated member values associated with other types of values

For example, in a network request, where there are basically only two cases of success and failure, and the result can only be one of these two, enumerations are better than structures and classes, and the result should only be success or failure, not both. Request results and data can be achieved through associated values in enumerations.

enum Response {
    case success([String:Any].String)
    case failure(Int.String)}var resp = Response.success(["name": "Swift"]."Request successful")
// resp =.failure(-1, "request failed ")
switch resp {
case let .success(data, msg):
  	// Server response success: data=["name": "Swift"] MSG = request success
    print("Server response success: data=\(data)\tmsg=\(msg)") 
case let .failure(errorCode, msg):
  	// Server response failure: errorCode=-1 MSG = Request failure
    print("Server response failure: errorCode=\(errorCode)\tmsg=\(msg)")}Copy the code

The value was successfully passed through the enumerated type

Memory analysis

To facilitate viewing the memory layout, change the associated value types to Int and Bool

enum TestEnum {
    case test1(Int)
    case test2(Int.Int)
    case test3(Int.Bool)}print(MemoryLayout<TestEnum>.size)          / / 17
print(MemoryLayout<TestEnum>.stride)        / / 24
print(MemoryLayout<TestEnum>.alignment)     / / 8
Copy the code

TestEnum size is 17 instead of 1.

An enumeration of associative values is similar to a common union in C/C++. Different cases use the same block of memory, so the memory allocated for the largest case must be selected. Int = 4 bytes; Bool = 1 byte; test2 = 16 bytes; As mentioned above, enumerations require 1 byte of memory to store member values, so TestEnum actually uses 17 bytes of memory. Due to memory alignment, the actual memory allocated is 24 bytes aligned with the alignment value.

var e1 = TestEnum.test1(1)
print(Mems.memStr(ofVal: &e1))		
// 0x0000000000000001 0x0000000000000000 0x0000000000000000
e1 = .test2(2.3)
print(Mems.memStr(ofVal: &e1))
// 0x0000000000000002 0x0000000000000003 0x0000000000000001
e1 = .test3(4.true)
print(Mems.memStr(ofVal: &e1))
// 0x0000000000000004 0x0000000000000001 0x0000000000000002
Copy the code

As you can see, the first 2*8 bytes are used to store the associated value, and the second *8+1 byte is used to store the member value

Raw Value

usage

The primitive value of an enumeration can be not only Int, UInt, but also String, Float, or Bool. Once the primitive value is declared, it can be obtained by evaluating the property via ra wValue, or by init? (rawValue: String) The constructor creates the enumeration

enum Direction : String {
    case east = "East"
    case south = "South"
    case west = "The west"
    case north = "North"
}

let direction1 = Direction.east
print("\(direction1) rawValue is: \(direction1.rawValue)") // East rawValue is: east
let d1 = Direction(rawValue: "East")
print("\(d1!) rawValue is: \(d1! .rawValue)") // East rawValue is: east
Copy the code

And the thing to notice is that init? (rawValue: String) The enumeration generated by the constructor is optional because there is no guarantee that the original value passed in by the user is correct

If we do not actively declare the original value, the compiler will automatically assign the original value:

enum Direction : String {
    case east
    case south
    case west
    case north
}

let direction1 = Direction.east
print("\(direction1) rawValue is: \(direction1.rawValue)") // east rawValue is: east
let d1 = Direction(rawValue: "east")
print("\(d1!) rawValue is: \(d1! .rawValue)") // east rawValue is: east
Copy the code

You can also implement the RawRepresentable protocol:

enum Direction : RawRepresentable {
    typealias RawValue = String
    case east
    case south
    case west
    case north
    init? (rawValue:Self.RawValue) {
        switch rawValue {
        case "East":
            self = .east
        case "South":
            self = .south
        case "The west":
            self = .west
        case "North":
            self = .north
        default:
            return nil}}var rawValue: String {
        switch self {
        case .east:
            return "East"
        case .south:
            return "South"
        case .west:
            return "The west"
        case .north:
            return "North"}}}let direction1 = Direction.west
print("\(direction1) rawValue is: \(direction1.rawValue)") // West rawValue is: west
let d1 = Direction(rawValue: "The west")
print("\(d1!) rawValue is: \(d1! .rawValue)") // West rawValue is: west
Copy the code

Once you declare RawRepresentable, the compiler doesn’t automatically generate your init? The (rawValue:) method and rawValue evaluate properties.

Analysis of the

The raw value of the enumeration takes no storage space

enum Direction : String {
    case east = "East"
    case south = "South"
    case west = "The west"
    case north = "North"
}

print(MemoryLayout<Direction>.size)          / / 1
print(MemoryLayout<Direction>.stride)        / / 1
print(MemoryLayout<Direction>.alignment)     / / 1
Copy the code

Where are the original values of the enumeration “east”, “south”, “west”, and “north”?

In fact, the compiler automatically implements rawValue for us, internally through switch, case returns the original value

let d = Direction.east
print(d.rawValue)
Copy the code

With LLDB, we get into the get method of the rawValue property of the Direction enumeration

Swift-CommandLine`Direction.rawValue.getter:
    0x1000020a0 <+0>:   pushq  %rbp
    0x1000020a1 <+1>:   movq   %rsp, %rbp
    0x1000020a4 <+4>:   subq   $0x30, %rsp
    0x1000020a8 <+8>:   movb   %dil, %al
    0x1000020ab <+11>:  movb   $0x0, -0x8(%rbp)
    0x1000020af <+15>:  movb   %al, -0x8(%rbp)
    0x1000020b2 <+18>:  movzbl %al, %edi
    0x1000020b5 <+21>:  movl   %edi, %ecx
    0x1000020b7 <+23>:  subb   $0x3, %al
    0x1000020b9 <+25>:  movq   %rcx, -0x10(%rbp)
    0x1000020bd <+29>:  movb   %al, -0x11(%rbp)
    0x1000020c0 <+32>:  ja     0x1000020d6               ; <+54> at <compiler-generated>
    0x1000020c2 <+34>:  leaq   0x9b(%rip), %rax          ; Swift_CommandLine.Direction.rawValue.getter : Swift.String + 196
    0x1000020c9 <+41>:  movq   -0x10(%rbp), %rcx
    0x1000020cd <+45>:  movslq (%rax,%rcx,4), %rdx
    0x1000020d1 <+49>:  addq   %rax, %rdx
    0x1000020d4 <+52>:  jmpq   *%rdx
    0x1000020d6 <+54>:  ud2    
    0x1000020d8 <+56>:  xorl   %edx, %edx
    0x1000020da <+58>:  leaq   0x51f0(%rip), %rdi        ; "\xe4\xb8\x9c"
    0x1000020e1 <+65>:  movl   $0x3, %esi
    0x1000020e6 <+70>:  callq  0x100006e1c               ; symbol stub for: Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String
    0x1000020eb <+75>:  movq   %rax, -0x20(%rbp)
    0x1000020ef <+79>:  movq   %rdx, -0x28(%rbp)
    0x1000020f3 <+83>:  jmp    0x10000214a               ; <+170> at main.swift
    0x1000020f5 <+85>:  xorl   %edx, %edx
    0x1000020f7 <+87>:  leaq   0x51d7(%rip), %rdi        ; "\xe5\x8d\x97"
    0x1000020fe <+94>:  movl   $0x3, %esi
    0x100002103 <+99>:  callq  0x100006e1c               ; symbol stub for: Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String
    0x100002108 <+104>: movq   %rax, -0x20(%rbp)
    0x10000210c <+108>: movq   %rdx, -0x28(%rbp)
    0x100002110 <+112>: jmp    0x10000214a               ; <+170> at main.swift
    0x100002112 <+114>: xorl   %edx, %edx
    0x100002114 <+116>: leaq   0x51be(%rip), %rdi        ; "\xe8\xa5\xbf"
    0x10000211b <+123>: movl   $0x3, %esi
    0x100002120 <+128>: callq  0x100006e1c               ; symbol stub for: Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String
    0x100002125 <+133>: movq   %rax, -0x20(%rbp)
    0x100002129 <+137>: movq   %rdx, -0x28(%rbp)
    0x10000212d <+141>: jmp    0x10000214a               ; <+170> at main.swift
    0x10000212f <+143>: xorl   %edx, %edx
    0x100002131 <+145>: leaq   0x51a5(%rip), %rdi        ; "\xe5\x8c\x97"
    0x100002138 <+152>: movl   $0x3, %esi
    0x10000213d <+157>: callq  0x100006e1c               ; symbol stub for: Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String
    0x100002142 <+162>: movq   %rax, -0x20(%rbp)
    0x100002146 <+166>: movq   %rdx, -0x28(%rbp)
    0x10000214a <+170>: movq   -0x28(%rbp), %rax
    0x10000214e <+174>: movq   -0x20(%rbp), %rcx
    0x100002152 <+178>: movq   %rax, -0x30(%rbp)
    0x100002156 <+182>: movq   %rcx, %rax
    0x100002159 <+185>: movq   -0x30(%rbp), %rdx
    0x10000215d <+189>: addq   $0x30, %rsp
    0x100002161 <+193>: popq   %rbp
    0x100002162 <+194>: retq   
Copy the code

First saw the first line of the location of the Swift – CommandLine ` Direction. The rawValue. Getter: clear where the assembly code

It doesn’t matter if I can’t read the assembly, I’m not very good at it myself, but I can find the key instructions

   	0x1000020da <+58>:  leaq   0x51f0(%rip), %rdi        ; "\xe4\xb8\x9c"
    0x1000020f7 <+87>:  leaq   0x51d7(%rip), %rdi        ; "\xe5\x8d\x97"
    0x100002114 <+116>: leaq   0x51be(%rip), %rdi        ; "\xe8\xa5\xbf"
    0x100002131 <+145>: leaq   0x51a5(%rip), %rdi        ; "\xe5\x8c\x97"
Copy the code

“\ xe4 \ xb8 \ x9c”, “\ xe5 \ x8d \ x97”, “\ xe8 \ xa5 \ XBF”, “\ xe5 \ x8c \ x97” corresponding “east”, “south”, “west”, “north” this four word utf-8 encoding.

To further verify the conjecture, let’s take a look at implementing the rawValue computed attribute in the RawRepresentable protocol ourselves

enum Direction : RawRepresentable {
    typealias RawValue = String
    case east
    case south
    case west
    case north
    init? (rawValue:Self.RawValue) {
        switch rawValue {
        case "East":
            self = .east
        case "South":
            self = .south
        case "The west":
            self = .west
        case "North":
            self = .north
        default:
            return nil}}var rawValue: String {
        switch self {
        case .east:
            return "East 1"
        case .south:
            return "South"
        case .west:
            return "The west"
        case .north:
            return "North"}}}Copy the code

In order to make a difference, I changed the “east” to “east 1” in the get method of ravValue.

Swift-CommandLine`Direction.rawValue.getter:
    0x1000020a0 <+0>:   pushq  %rbp
    0x1000020a1 <+1>:   movq   %rsp, %rbp
    0x1000020a4 <+4>:   subq   $0x30, %rsp
    0x1000020a8 <+8>:   movb   %dil, %al
    0x1000020ab <+11>:  movb   $0x0, -0x8(%rbp)
    0x1000020af <+15>:  movb   %al, -0x8(%rbp)
  	0x1000020b2 <+18>:  movzbl %al, %edi
    0x1000020b5 <+21>:  movl   %edi, %ecx
    0x1000020b7 <+23>:  subb   $0x3, %al
    0x1000020b9 <+25>:  movq   %rcx, -0x10(%rbp)
    0x1000020bd <+29>:  movb   %al, -0x11(%rbp)
    0x1000020c0 <+32>:  ja     0x1000020d6               ; <+54> at main.swift:356:14
    0x1000020c2 <+34>:  leaq   0x9b(%rip), %rax          ; Swift_CommandLine.Direction.rawValue.getter : Swift.String + 196
    0x1000020c9 <+41>:  movq   -0x10(%rbp), %rcx
    0x1000020cd <+45>:  movslq (%rax,%rcx,4), %rdx
    0x1000020d1 <+49>:  addq   %rax, %rdx
    0x1000020d4 <+52>:  jmpq   *%rdx
    0x1000020d6 <+54>:  ud2    
    0x1000020d8 <+56>:  xorl   %edx, %edx
    0x1000020da <+58>:  leaq   0x5200(%rip), %rdi        ; "\xe4\xb8\x9c1"
    0x1000020e1 <+65>:  movl   $0x4, %esi
    0x1000020e6 <+70>:  callq  0x100006e1c               ; symbol stub for: Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String
    0x1000020eb <+75>:  movq   %rax, -0x20(%rbp)
    0x1000020ef <+79>:  movq   %rdx, -0x28(%rbp)
    0x1000020f3 <+83>:  jmp    0x10000214a               ; <+170> at main.swift
    0x1000020f5 <+85>:  xorl   %edx, %edx
    0x1000020f7 <+87>:  leaq   0x51d7(%rip), %rdi        ; "\xe5\x8d\x97"
    0x1000020fe <+94>:  movl   $0x3, %esi
    0x100002103 <+99>:  callq  0x100006e1c               ; symbol stub for: Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String
    0x100002108 <+104>: movq   %rax, -0x20(%rbp)
    0x10000210c <+108>: movq   %rdx, -0x28(%rbp)
    0x100002110 <+112>: jmp    0x10000214a               ; <+170> at main.swift
    0x100002112 <+114>: xorl   %edx, %edx
    0x100002114 <+116>: leaq   0x51be(%rip), %rdi        ; "\xe8\xa5\xbf"
    0x10000211b <+123>: movl   $0x3, %esi
    0x100002120 <+128>: callq  0x100006e1c               ; symbol stub for: Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String
    0x100002125 <+133>: movq   %rax, -0x20(%rbp)
    0x100002129 <+137>: movq   %rdx, -0x28(%rbp)
    0x10000212d <+141>: jmp    0x10000214a               ; <+170> at main.swift
    0x10000212f <+143>: xorl   %edx, %edx
    0x100002131 <+145>: leaq   0x51a5(%rip), %rdi        ; "\xe5\x8c\x97"
    0x100002138 <+152>: movl   $0x3, %esi
    0x10000213d <+157>: callq  0x100006e1c               ; symbol stub for: Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.String
    0x100002142 <+162>: movq   %rax, -0x20(%rbp)
    0x100002146 <+166>: movq   %rdx, -0x28(%rbp)
    0x10000214a <+170>: movq   -0x28(%rbp), %rax
    0x10000214e <+174>: movq   -0x20(%rbp), %rcx
    0x100002152 <+178>: movq   %rax, -0x30(%rbp)
    0x100002156 <+182>: movq   %rcx, %rax
    0x100002159 <+185>: movq   -0x30(%rbp), %rdx
    0x10000215d <+189>: addq   $0x30, %rsp
    0x100002161 <+193>: popq   %rbp
    0x100002162 <+194>: retq   
Copy the code

It looks pretty much the same as the compiler’s auto-generated assembly, but some might say I just copied the compiler’s auto-generated assembly. Carefully compare the assembly instructions for address 0x1000020da in the two assembly segments:

// the compiler generates 0x1000020DA <+58>: leaq 0x51f0(%rip), %rdi; "\xe4\xb8\x9c" 0x1000020e1 <+65>: movl $0x3, %esi 0x1000020e6 <+70>: callq 0x100006e1c ; symbol stub for: Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.int1) -> swift. String // self-implement 0x1000020da <+58>: leaq 0x5200(%rip), %rdi; "\xe4\xb8\x9c1" 0x1000020e1 <+65>: movl $0x4, %esi 0x1000020e6 <+70>: callq 0x100006e1c ; symbol stub for: Swift.String.init(_builtinStringLiteral: Builtin.RawPointer, utf8CodeUnitCount: Builtin.Word, isASCII: Builtin.Int1) -> Swift.StringCopy the code

The compiler generates the 0x51f0(%rip) address to store “\xe4\ XB8 \x9c”=” east “. The UTF-8 encoding is 0x3;

0x5200(%rip) address store is “\xe4\ XB8 \ x9C1 “=” east 1″, UTF-8 encoding length is 0x4

The compiler automatically generates the rawValue calculation property and returns the rawValue based on the enumerated value, switch, and case