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