The enumeration

Basic use of enumeration

In Swift, an enumeration is declared with the enum keyword, which, like a struct, is a value type. Methods can be added, properties can be calculated, protocols can be followed, and extensions can be supported.

The basic use of enumerations in Swift is as follows:

enum FWJEnum {
    case Test_One
    case Test_Two
    case Test_Three
}
Copy the code

You can also complete multiple enumerations in the same case

enum FWJEnum {
    case Test_One.Test_Two.Test_Three
}
Copy the code

Enumerate raw values

In OC and C, enumerations mainly support integer types, as in the following example: A, B, and C default to 0,1,2 respectively

typedef NS_ENUM(NSUInteger, LGEnum) { 
    A,
    B,
    C, 
};
Copy the code

Enumerations in Swift are more flexible and do not need to provide a value for every member of the enumeration. If a value (the “raw” value) is to be supplied to each enumerator, it can be a string, a character, an arbitrary integer value, or a floating-point type.

enum Color: String {
    case red = "Red" 
    case amber = "Amber" 
    case green = "Green"
}

enum FWJEnum: Double {
    case a = 10.0 
    case b= 20.0 
    case c = 30.0 
    case d = 40.0
}
Copy the code

Implicit RawValue

In swift, the RawValue of an enumeration can be set automatically through type push, which is called an implicit RawValue. Let’s take a look at this case.

enum DayOfWeek: Int {
  case mon, tue, wed, thu, fri = 10, sat, sun
}
print(DayOfWeek.mon.rawValue) / / 0

print(DayOfWeek.tue.rawValue) / / 1

print(DayOfWeek.fri.rawValue)/ / 10

print(DayOfWeek.sun.rawValue)/ / 12
Copy the code

The mon value starts at 0, but when frI is set to 10, the values of sat and Sun are added to the frI value, that is, sat and Sun are 11 and 12, respectively.

If the enumeration value is set to string, the system compiler will automatically treat the string with the same name as the original case value when the case is not set. You can also manually set the case raw value.

enum DayOfWeek: String {
    case mon, tue, wed, thu, fri = "Fri", sat, sun

}

print(DayOfWeek.mon.rawValue) //mon

print(DayOfWeek.fri.rawValue) //Fri

print(DayOfWeek.sat.rawValue) // sat

Copy the code

So how does the enumeration primitive get this string? Let’s open the SIL file and take a look.

enum DayOfWeek: String {
    case mon, tue, wed, thu, fri = "Fri", sat, sun

}
let rawValue = DayOfWeek.mon.rawValue
Copy the code

So let’s take a lookenumType declarationYou can see that there is a failable initializer in the declarationinit? (rawValue:String), and one can onlygetComputing attributes ofrawValue.

Let’s look at the get method of rawValue next.

In the SIL code above, do a pattern matchswift_enum %0Here,% 0The current enumeration value is passed in. So what we’re passing in is thetamon, so the matched code iscase #DayOfWeek.mon! enumelt: bb1“And then we enteredbb1Code modules.

In the bb1 code block, the direct string creates “mon”. In sil, the enumeration compiler automatically generates rawValue calculated properties and initialization methods. The calculated properties are essentially methods, and the rawValue for each enumerated value is determined at compile time and does not need to be stored.

So this stringmonWhere did you get it? Such a string is actually a string constant, and where the string constant is stored, let’s drag the Mach-O file to the MachoView application to see.

Enumeration can fail initializers

The enumeration value inside the enumeration and the original value are two different types, and we use the one aboveDayOfWeekLet me illustrate. Like enumerated valuesmonAnd its type isDayOfWeek. The original value although the default value ismonBut its type isStringType. So we can’t create an enumerated value directly from a string. In Swift, a failable initializer is providedinit? (rawValue:String)To initialize the enumeration value, let’s look at the sil file to see how the enumeration value is initialized. Through sil can be found in the callInit (rawValue:)When, first will put allcaseThe corresponding string is stored in a contiguous memory space. And then based on the incomingrawValueTo match the string in this space to the enumeration value, it is possible to construct an enumeration instance from the original value, because the original value passed in does not necessarily correspond to an enumeration value. So this method actually returns an Optional value of type Optional, or nil if the construct fails.

The associated values

In Swfit, enumerations are not just used to describe simple types. You can describe complex data models by associating them with other data types using associated values.

Pattern matching

Swift wants to match all of themcaseIf you don’t want to match all of themcase, you can usedefaultInstead.

Memory size of enumeration

Front we know the basic usage of enumeration, also know it and can have original values and the associated values, the original value is calculated attribute won’t be stored in memory, so the memory information there will be no influence to the enumeration, difficult? What effect will the associated values of storage for enums, below to explore in the following several conditions enumerated values occupy memory size

Enumeration value has no associated value

Let’s look at the uncorrelated values firstsizeandstrideBoth of these values are equal to 1. This is easy to understand because of the implicit nature of enumerationrawValueIt’s already stored in Mach-O as hard code. So enumerations now only need to store enumeration values. For the current enumeration value, Swift defaults toUInt8Type, which is 1 byte. Let’s look at how enumerated values are stored.By printing the memory of a, B, and C variables. You can see that each enumeration value is stored in memory for only one byte, and that three consecutive enumeration values differ by one byte in memory address, and their value in memory is0x0,0x1,0x2This adds up to memory for identification. This means that an enumeration value is 1 byte in size and can store at least 256 enumerations.

Enumeration values have only one associated value

So let’s look at just one associationBOOLValue of You can see that the size of each enumeration (size) and step size (stride) are all 1. This is becauseBoolThe type is 1 byte, which isUInt8, so currently 256 can be expressedcaseFor Boolean types, only the low values of 0 and 1 are used, and the remaining space can be used to represent no loadcaseValue. By printing the memory address, you can see the differencecaseThe values are really laid out according to the conclusion we came up with at the beginning.

Now let’s see there’s only oneIntCase of associated values And you can see thatBOOLThe size of each enumeration (size) is 9, step size (stride) to 16. This is because of theIntType of load, in fact, the system is no way to calculate the current load to use the number of bits, which means the currentIntThe type of load has no extra free space, at which point we need to open up extra memory space to store our loadcaseValue, which is 8 + 1 = 9 bytes.

Enumerated values have multiple associated values

Let’s first take a look at the case where all the associated values are of the same type

As you can see, each enumeration has a size and stride of 1. This is because the associated values are all BOOL types, and the enumerated size of BOOL types is described above.

Let’s look at the memory distribution Here we can see that the current memory store is 00, 01, 40, 41, 80,81This is because forboolIn terms of types, what we store is nothing more than0or1We only need 1 bit, so the other 7 bits are all calledcommon spare bitsFor the current number of cases we can put all the cases in common spare bits So here we only need 1 byte to store everything.

Now let’s see what 00, 01, 40, 41, 80 and 81 represent. 0,4,8 is a tag value, 0,1 is a tag value, 0,1 is a tag value, 0,1 is a tag value

Let’s look at what happens when multiple associative values are of different types

The enumeration size is 9 and the step size is 16. This is because when we have multiple enumerations of associated values, the size of the current enumeration type depends on the size of the current maximum associated value. So the sizeof the current enumeration is equal to sizeof(Int) + sizeof(rawVlaue) = 9, up to 16 based on memory alignment.

If I have the following situationSo the current enumeration size issizeof(Int) * 3 + sizeof(rawVlaue) = 25.

Let’s look at another exampleAs you can see, when the type order of the associated values changes, the size of the enumeration is different. The first oneLGEnumThe size of theta is 25, and thetaLGEnum1The size of PI becomes 32. This is becauseboolWhen the type is in the middle, the internal bits of the enumeration can quickly read and store each associated value, memory alignment, each associated value is 8 bytes,boolThe type is padded up to eight bytes,boolWhen the position of the type is at the end, the system knows exactly what the last bit is when it calculates memoryboolThe type is 1 bit, so there is no need to move the pointer because there is no alignment, so it is 25 bytes, but according to the principle of memory alignment, the current enumeration variable will be allocated to fill up memory.

Enumerates memory size summaries

When enumeration values associated with it the storage size, and associated value type and quantity, when the associated value types have additional space can be of value, the system will store the extra space use enumerated values, when there is no extra space, the system will according to the associated value types required size + store a byte of the enumeration values As the length of the actual need, Then according to the alignment principle to do memory completion.

Single enumerated value

When an enumeration has only one enumeration value, we don’t need anything to distinguish the current enumeration value, so when we print the current enumeration size you’ll find it is 0.

Recursive enumeration

Recursive enumerations are enumerations that have another enumeration associated with a value as an enumerator. The indirection layer must be inserted when the compiler operates on recursive enumerations. You can specify that the enumerator is recursive by using the indirect keyword before declaring it.

enum ArithmeticExpression {
    case number(Int)
    indirect case addition(ArithmeticExpression.ArithmeticExpression)
    indirect case multiplication(ArithmeticExpression.ArithmeticExpression)}Copy the code

You can also write indirect before the enumeration to make the entire enumerator recursive if needed:

indirect enum ArithmeticExpression {
    case number(Int)
    case addition(ArithmeticExpression.ArithmeticExpression)
    case multiplication(ArithmeticExpression.ArithmeticExpression)}Copy the code

We can then complete an expression with the recursive enumeration defined above :(5 + 4) * 2

let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2)) 

func evaluate(_ expression: ArithmeticExpression) -> Int {

    switch expression {
        case let .number(value):
            return value
        case let .addition(left, right):
            return evaluate(left) + evaluate(right)
        case let .multiplication(left, right):
            return evaluate(left) * evaluate(right)
    }
}

print(evaluate(product))

// Prints "18"
Copy the code

An optional value

We’ve already touched on optional values in our code. For example, we defined them in code like this:

class LGTeacher {
    var age: Int?
}
Copy the code

The current age is what we call optional.

We can also write optional values like this.

var age: Int? = var age: Optional<Int>
Copy the code

What is the nature of that for Optional? Let’s jump straight to the source and open the optional. swift file

@frozen
public enum Optional<Wrapped> {
    case none
    case some(Wrapped)}Copy the code

As you can see, the optional value Option is actually an enumeration type that returns None when it is null and the current value when it has a value.

Optional Value Forcibly unbind

When you are sure that the optional value you defined has a value, you can use! To force unbinding the optional value.

var age: Int? = 10
let age2 = age!
print(age2)
Copy the code

Optional value binding

We can check whether this option has a value by using the if statement as follows:

var age: Int?
if age == nil {
print("age is nil")
}else {
print("age is (age!)")
}
Copy the code

In addition to this way, we can also use optional binding to determine whether the optional has a value and pull it out. If the option contains a value, it is automatically unpacked, assigned to a temporary constant (let) or variable (var), and returns a Bool.

The code is as follows:

var age: Int? = 10
if let age = age {
print("age is (age)")}else {
print("age is nil")}Copy the code

Optional chain

We all know that in OC nothing happens if we send a message to a nil object, and in Swift we can’t send a message directly to a nil object, but we can do something similar with an optional chain. Let’s look at the next two pieces of code

let str: String? = "abc" 
let upperStr = str?.uppercased() // Optional<"ABC">

var str1: Stringlet upperStr1 = str?.uppercased() // nil
Copy the code

As you can see, STR capitalizes when it has an initial value, returning an Optional<“ABC”>, and nil when it has no value.

So what does this code say

let str: String? = "abc"
let upperStr = str?.uppercased().lowercased() // Optional<"abc">
Copy the code

The same optional chain applies to subscripts and function calls

var closure: ((Int) - > ())? 
closure?(1) // Closure is nil and is not executed
let dict = ["one": 1."two": 2] 
dict?["one"] // Optional(1)
dict?["three"] // nil
Copy the code

?? The operator

( a ?? B) The optional type a is nullified and unpacked if a contains a value, otherwise a default value b is returned.

  • expressionaIt must beOptionalType.
  • The default valuebThe type must be andaThe types of stored values remain the same.

The hermit resolves optional types

Implicit resolution of optional types is one type of optional type and is used in the same way as non-optional types. The only difference between them is that the implicit resolution optional type is you tell the Swift compiler to say, when I access at run time, the value won’t be nil.

var age: Int? 
var age1: Int!// The hermit resolves optional types
age = nil 
age1 = nil
Copy the code