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