preface

The definition and usage of enumerated values are not mentioned here, but it has to be said that Enum in Swift is very powerful, it not only has the original value of the traditional language, but also can put the value of the associated type, and even define class attributes, counting attributes, etc., so behind such a powerful function, how to achieve the bottom layer?

Enumeration in the source code implementationEnumMetadata

EnumMetadata and StructMetadata are basically the same, but there is no need to elaborate on them. And as usual, I also translated EnumMetadata source code into swift code implementation, GitHub address is here.

The only difference between EnumMetadata and StructMetadata is that The Description of the properties NumFields and FieldOffsetVectorOffset became NumPayloadCasesAndPayloadSizeOffset and NumEmptyCases.

Low NumPayloadCasesAndPayloadSizeOffset is a UInt32, 24 deposited with a number of associated values enumeration, high put maximum payload number eight. NumEmptyCases puts the number of unassociated value enumerations, that is, enumerations with original values or none at all. Take low after 24 plus NumEmptyCases NumPayloadCasesAndPayloadSizeOffset, can get the total number of enumeration, translation of the code above is true.

Enumeration structure in memory

Let’s start with the simplest code:

enum Animal {
    case bird
    case cat
    case dog
}
var obj = Animal.dog
withUnsafePointer(to: &obj) {
    print($0)}Copy the code

Take a look at what is stored in memory:

Animal. Bird is 0, Animal. Cat is 1. All of this should come as no surprise, because in OC or C, if you don’t assign values to enumerations, their enumerations go from top to bottom, starting at zero.

Enumerates the underlying implementation of the original value

If we add the original value to the enumeration we just didrawValueWhat would it be like? Let’s take a look:

Animal. Dog still has a 2 in memory instead of the 7 we assigned, so where is rawValue that we assigned?

Let’s convert the following code into a sil file:

enum Animal: Int {
    case bird  = 10
    case cat   = 27
    case dog   = 7
}
let value = Animal.dog.rawValue
Copy the code

Enum definition in sil file:

enum Animal : Int {
  case bird
  case cat
  case dog
  typealias RawValue = Int
  init?(rawValue: Int)
  var rawValue: Int { get}}Copy the code

We can see that the compiler automatically adds rawValue to the computation property, because rawValue is a calculation property and is not stored in memory, so we can’t see the rawValue value when printing memory. So how do you get rawValue? Because the sil file is full of judgment statement code, it is too long, so I switch to swift.

enum Animal: Int {
    case bird  = 10
    case cat   = 27
    case dog   = 7
    
    var rawValue: Int {
        switch self {
        case .bird:
            return 10
        case .cat:
            return 27
        case .dog:
            return 7}}init?(rawValue: Int) {
        if rawValue = = 10 {
            self = .bird
        } else if rawValue = = 27 {
            self = .cat
        } else if rawValue = = 7 {
            self = .dog
        } else {
            return nil}}}Copy the code

That’s the underlying logic, and the interesting thing is that if we implement these methods ourselves, the compiler won’t automatically generate them. For example, you are matching to.dogIs returned8, then you getrawValueWhen you get to8It’s not your definition7, like this:

CaseIterableagreement

We sometimes need to iterate through an enumeration or know the number of enumerations. I don’t know what you did in OC, but I used macros. In Swift, however, this is easy to do automatically by making the enumeration comply with the CaseIterable protocol. Let’s look at the CaseIterable protocol definition:

public protocol CaseIterable {

    /// A type that can represent a collection of all values of this type.
    associatedtype AllCases : Collection where Self = = Self.AllCases.Element

    /// A collection of all values of this type.
    static var allCases: Self.AllCases { get}}Copy the code

After compliance, the class attribute allCases, which complies with the Collection protocol, can iterate through all of its enumerations:

extension Animal: CaseIterable {}let x = Animal.allCases
Animal.allCases.forEach {
    print($0)}Copy the code

As for how it works, emmmm, like above, is done for you by the compiler. Let’s take a look at the SIL file:

It becomes an array, puts all the enumerations in the array, and returns. However, this process is generated by the compiler, so there is no case where enumerations are added later and new enumerations are forgotten in the business code.

The associated value of an enumeration

Let’s write a simple enumeration:

enum Animal {
    case bird(Int)
    case cat(Int.Double)
    case dog(String.Double.Int)}var obj = Animal.cat(22.1.5)
Copy the code

Take a look at the sil file:

We can see that enumerations keep the original definition and do not implement any additional functions for us. When we generate enumerations, we create a meta-ancestor first and then give the meta-ancestor to the enumerations for initialization, indicating that all the associated values of the enumerations are meta-ancestor types.

Since the SIL file doesn’t show much, why don’t we explore its memory

Memory size of enumeration

Enumeration with no original value and no associated value

enum Animal {
    case bird
    case cat
    case dog
}

print("size: \(MemoryLayout<Animal>.size)")
print("stride: \(MemoryLayout<Animal>.stride)")
print("alignment: \(MemoryLayout<Animal>.alignment)")

/** size: 1 stride: 1 alignment: 1 */
Copy the code

As we have said above, enumeration value stored in memory is the value that starts from 0 and rises successively, and a word energy saving represents 256 numbers. Therefore, when the number of enumeration value is below 256, his size and stride are 1. When the number exceeds 256, the size and stride both become 2. I have tested this and will not show it.

Enumeration with raw values

enum Animal: String {
    case bird
    case cat
    case dog
}

print("size: \(MemoryLayout<Animal>.size)")
print("stride: \(MemoryLayout<Animal>.stride)")
print("alignment: \(MemoryLayout<Animal>.alignment)")

/** size: 1 stride: 1 alignment: 1 */
Copy the code

Enumerations with raw values have the rawValue property, but the rawValue property is a calculated property and does not consume memory, so it is exactly the same as enumerations with no raw values.

Enumeration of associated values

enum Animal {
    case bird(Int)
    case cat(Int.Double)
    case dog(String.Double.Int)}print("size: \(MemoryLayout<Animal>.size)")
print("stride: \(MemoryLayout<Animal>.stride)")
print("alignment: \(MemoryLayout<Animal>.alignment)")

/** size: 33 stride: 40 alignment: 8 */
Copy the code

We see that the alignment size is 8, so the stride is slightly larger than the size and a multiple of the alignment, that is, 40. All these are ok. The key is how size gets to 33. Let’s tidy up the base type size, String is 16 bytes, Int is 8 bytes, Double is 8 bytes, and the enumeration itself is 1 byte, which adds up to exactly 33 bytes. Can we guess that the size of the associated value enumeration depends on the size of the largest progenitor type and the size of the enumeration itself?

Let’s first look at the values in memory:

Circled by red boxes: ABC of String, 55 of Double, 42 of Int, and the last enumeration, 2, are exactly 33 bytes.

The memory structure of the associative value enumeration is the data in front of the ancestor followed by the enumeration value, so the size of the associative value enumeration is basically equal to the size of the associative value ancestor plus the size of the enumeration itself. Of course, there may be a bit of discrepancy, and memory alignment, this exploration of the situation is more, interested in your own test.

indirectKeyword underlying analysis

Sometimes we come across enumerations nesting themselves, and we need to use our indirect keyword.

For example, we define an enumeration of a linked list structure:

enum List<T>{
    case end
    indirect case node(T, next: List<T>)}Copy the code

It can also be written like this:

indirect enum List<T>{
    case end
    case node(T, next: List<T>)}Copy the code

We know that enumeration is a value type, so we can determine the size before compiling, but we can’t determine the size if we want to nest ourselves indefinitely.

Let’s first look at the size of the enumeration:

print("size: \(MemoryLayout<List<String>>.size)")
print("stride: \(MemoryLayout<List<String>>.stride)")
print("alignment: \(MemoryLayout<List<String>>.alignment)")

/** size: 8 stride: 8 alignment: 8 */
Copy the code

We defined the template as String, String itself is 16 bytes, so the enumeration must be at least 16 bytes larger, it turns out to be 8 bytes. Let’s take a look at memory:

Obviously, a pointer is stored in memory, so an enumeration decorated with the indirect keyword becomes a pointer type.

We’re looking at an amazing comparison:

And:

Since the indirect keyword is placed before the enum, each option in the enumeration becomes a pointer, which is a reference type. Placing the indirect keyword before a single case will only change the individual case to a reference type.

How can I turn a value type into a reference type? How can I turn a value type into a reference type?

Swift_allocBox (” swift_allocBox “);

struct BoxPair {
  HeapObject *object;
  OpaqueValue *buffer;
};

BoxPair swift::swift_allocBox(const Metadata *type) {
  // Get the heap metadata for the box.
  auto metadata = &Boxes.getOrInsert(type).first->Data;

  // Allocate and project the box.
  auto allocation = swift_allocObject(metadata, metadata->getAllocSize(),
                                      metadata->getAllocAlignMask());
  auto projection = metadata->project(allocation);

  return BoxPair{allocation, projection};
}

Copy the code

We see swift_allocBox opened up a piece of space on the heap, then put the value after the HeapObject, depending on, can search TargetGenericBoxHeapMetadata source and don’t watch, a bit digress.

conclusion

There’s a lot more to enumerations, but they’re really powerful.

Will not conclude, like this article upvote = =