The structure of the body
The basic concept
In the Swift standard library, the vast majority of exposed types are structures, while enumerations and classes make up only a small portion
Common types such as Bool, Int, String, Double, Array, and Dictionary are all structures
struct Date {
var year: Int
var month: Int
var day: Int
}
Copy the code
All constructs have an incubator (initializer, initializer, constructor, constructor) automatically generated by the compiler.
You can pass in all member values to initialize all members (storing properties, Stored Property)
var date = Date(year: 2019, month: 6, day: 23)
Copy the code
Initializer for a structure
Depending on the situation, the compiler may generate multiple initializers for the structure. The purpose is to ensure that all members have initial values
If the members of a structure are defined with default values, the generated initializer will not report an error
An error is reported in any of the following cases
The initializer will not fail if it is an optional type, because the default value of the optional type is nil
Custom initializers
We can also customize the initializer
struct Point {
var x: Int = 0
var y: Int = 0
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
var p1 = Point(x: 10, y: 10)
Copy the code
The reason the following initializers fail is because we have already defined our own initializers, and the compiler will not generate any more initializers for us
The nature of the initializer
The following two ways are exactly equivalent
Struct Point {var x: Int = 0 var y: Int = 0} Int init() { x = 0 y = 0 } } var p4 = Point()Copy the code
We compare the two implementations separately through disassembly and find the same
1. Then we print the size and layout of the structure respectively
struct Point {
var x: Int = 10
var y: Int = 20
}
var p4 = Point()
debugPrint(MemoryLayout<Point>.stride) // 16
debugPrint(MemoryLayout<Point>.size) // 16
debugPrint(MemoryLayout<Point>.alignment) // 8
debugPrint(Mems.memStr(ofVal: &p4)) // 0x000000000000000a 0x0000000000000014
Copy the code
You can see that the system has allocated 16 bytes of memory
The first eight bytes store 10, and the last eight bytes store 20
2. Let’s look at the structure below
struct Point {
var x: Int = 10
var y: Int = 20
var origin: Bool = false
}
var p4 = Point()
debugPrint(MemoryLayout<Point>.stride) // 24
debugPrint(MemoryLayout<Point>.size) // 17
debugPrint(MemoryLayout<Point>.alignment) // 8
debugPrint(Mems.memStr(ofVal: &p4)) // 0x000000000000000a 0x0000000000000014 0x0000000000000000
Copy the code
You can see that the structure actually uses only 17 bytes, whereas the system allocated 24 bytes for memory alignment
The first eight bytes store 10, the middle eight bytes store 20, and the last byte stores false, which is 0
class
The definition of a class is similar to that of a structure, but the compiler does not automatically generate initializers for classes that can pass in member values
If the member has no initial value, all initializers report an error
Class initializer
If all the members of a class have specified initializers when they are defined, the compiler generates an initializer with no arguments for the class
The initialization of a member is done in this initializer
class Point {
var x: Int = 0
var y: Int = 0
}
let p1 = Point()
Copy the code
The following two ways are exactly equivalent
Class Point {var x: Int = 0 var y: Int = 0} class Point {var x: Int y: Int = 0} Int init() { x = 0 y = 0 } } let p1 = Point()Copy the code
Essential differences between structures and classes
Structs are value types (enumerations are also value types) and classes are reference types (pointer types)
Where in memory are local variables in a function
class Size {
var width = 1
var height = 2
}
struct Point {
var x: Int = 3
var y: Int = 4
}
func test() {
var size = Size()
var point = Point()
}
Copy the code
The variables size and point are both in stack space
The difference is that the value of point is a structure type, and the structure is also a value type, so its two members x and y are arranged in order in the stack space
Size is a class, and class is a reference type, so size is a pointer to a class that’s in the heap, and size stores the address of that class
Let’s examine the detailed memory layout of the class
1. Let’s take a look at how much memory the class occupies
class Size {
var width = 1
var height = 2
}
debugPrint(MemoryLayout<Size>.stride) // 8
Copy the code
By printing, we can see that the eight bytes MemoryLayout gets are actually how much storage the pointer variable takes up, not how much the object takes up in the heap
2. Then we will look at the memory layout of the class
var size = Size()
debugPrint(Mems.ptr(ofVal: &size)) // 0x000000010000c388
debugPrint(Mems.memStr(ofVal: &size)) // 0x000000010072dba0
Copy the code
By printing, we can see that the value stored in the variable is also an address
3. What is the memory layout of the object pointed to by this variable
debugPrint(Mems.size(ofRef: size)) // 32
debugPrint(Mems.ptr(ofRef: size)) // 0x000000010072dba0
debugPrint(Mems.memStr(ofRef: size)) // 0x000000010000c278 0x0000000200000003 0x0000000000000001 0x0000000000000002
Copy the code
By printing, you can see that the address of the object stored in the heap is the same as the value stored in the pointer variable above
The memory layout takes up 32 bytes. The first 16 bytes are used to store class information and reference counts, and the last 16 bytes are used to store the values of class member variables
Now let’s analyze it from the disassembly point of view
To determine whether the class allocates space in the heap, disassemble to see if the malloc function is called
And then I’m going to follow it all the way up to where swift_slowAlloc is best called
The system malloc was called internally to allocate memory in the heap space
Note: Where structures and enumerations are stored depends on where they are allocated, in the stack if they are allocated in a function, in the data segment if they are allocated globally. Okay
Whereas wherever the class is allocated, the object is in the heap space, and the location of the pointer to the object’s memory is indeterminate, either in the stack or in the data segment. Right
Let’s look at the memory footprint of the following types
class Size {
var width: Int = 0
var height: Int = 0
var test = true
}
let s = Size()
print(Mems.size(ofRef: s)) // 48
Copy the code
The malloc function on Mac and iOS always allocates a multiple of 16
The first 16 bytes of the class are used to store the information and reference count of the class, so the actual memory usage is 33 bytes, but since the malloc function allocates memory in multiples of 116, 48 bytes are allocated
We can also get the memory size of the class object by using the class_getInstanceSize function
Print (class_getInstanceSize(type(of: s))) // 40 print(class_getInstanceSize(Size.self)) // 40Copy the code
Value type and reference type
Value types
When a value type is assigned to a var, let, or function, it copies everything directly
This operation is similar to copying or paste a file to create a new copy of the file, which is called deep copy.
Value type The memory layout for copying is as follows
struct Point {
var x: Int = 3
var y: Int = 4
}
func test() {
var p1 = Point(x: 10, y: 20)
var p2 = p1
p2.x = 4
p2.y = 5
print(p1.x, p1.y)
}
test()
Copy the code
We do the analysis through disassembly
From the above analysis, it can be found that the assignment of value type will first save the member values of P1 and then assign values to P2, so p1 will not be affected
The assignment operation of a value type
In Swift standard library, Copy On Write technology is adopted for Array, String, Dictionary, and Set to improve performance
If only the assignment is performed, then only a shallow copy is made, and both variables still use the same block of storage
A deep copy operation is performed only when a write operation is performed
Swift ensures optimal performance for assignment operations of library value types, so there is no need to avoid assignment for optimal performance
var s1 = "Jack"
var s2 = s1
s2.append("_Rose")
print(s1) // Jack
print(s2) // Jack_Rose
Copy the code
var a1 = [1, 2, 3]
var a2 = a1
a2.append(4)
a1[0] = 2
print(a1) // [2, 2, 3]
print(a2) // [1, 2, 3, 4]
Copy the code
var d1 = ["max" : 10, "min" : 2]
var d2 = d1
d1["other"] = 7
d2["max"] = 12
print(d1) // ["other" : 7, "max" : 10, "min" : 2]
print(d2) // ["max" : 12, "min" : 2]
Copy the code
** Note: ** does not need to modify as much as possible to let
Let’s look at the code below
For P1, the reassignment only overwrites the values of members X and y, both of which are the same structure variables
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10, y: 20)
p1 = Point(x: 11, y: 22)
Copy the code
Assignment operations defined with let
An error is reported if a structure type is assigned by a let constant, and an error is reported if a member value is changed in the structure
The let definition means that the value stored in the constant cannot be changed, and the structure is made up of 16 bytes x and y, so changing x and y means that the structure value will be overwritten, so an error is reported
Reference types
When a reference is assigned to a var, let, or function, it makes a copy of the memory address
Shallow copy Similar to creating a double for a file (shortcut, link) that points to the same file and is a shallow copy
class Size {
var width = 0
var height = 0
init(width: Int, height: Int) {
self.width = width
self.height = height
}
}
func test() {
var s1 = Size(width: 10, height: 20)
var s2 = s1
s2.width = 11
s2.height = 22
print(s1.height, s1.width)
}
test()
Copy the code
Since s1 and S2 both refer to the same block of storage, S2 modifies the member variable, and s1 calls the member variable after the change
We do the analysis through disassembly
After the heap space is allocated, we get the value of RAX and check the memory layout. We find that the structure of RAX is the same as that of the object, which proves that raX stores the address of the object
Overwrite the heapspace object’s member values with the new values 11 and 22, respectively
According to the above analysis, it can be found that the modified member values are all objects with the same address, so the modification of p2 member values will affect P1
An assignment operation of a reference type
Assigning a reference type object to the same variable points to another block of storage
class Size {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
}
var s1 = Size(width: 10, height: 20)
s1 = Size(width: 11, height: 22)
Copy the code
Assignment operations defined with let
An error is reported if a reference type is assigned to a let constant because it changes the 8-byte address value stored in the pointer constant
However, modifying a property value in a class does not give an error, because modifying a property value is not modifying the memory of the pointer constant, but modifying the property of the class by finding the memory address of the heap space stored by the pointer constant
Nested types
Struct Poker {enum Suit: Character {case spades = "♠️", hearts = "♥️", diamonds = "♦️", clubs = "♣️"} enum Rank: Int { case two = 2, three, four, five, six, seven, eight, nine, ten case jack, queen, king, ace } } print(Poker.Suit.hearts.rawValue) var suit = Poker.Suit.spades suit = .diamonds var rank = Poker.Rank.five rank = .kingCopy the code
Enumerations, structures, and classes can all define methods
Functions defined in enumerations, structures, and classes are generally called methods
struct Point {
var x: Int = 10
var y: Int = 10
func show() {
print("show")
}
}
let p = Point()
p.show()
Copy the code
class Size {
var width: Int = 10
var height: Int = 10
func show() {
print("show")
}
}
let s = Size()
s.show()
Copy the code
enum PokerFace: Character {case spades = "♠️", hearts = "♥️", diamonds = "♦️", Clubs = "♣️" func show() {print("show")}} let pf = pokerface.hearts pf.show()Copy the code
The memory of a method is in a code block, no matter where it’s placed, right
Methods in enumerations, structures, and classes actually have implicit arguments
Class Size {var width: Int = 10 var height: Int = 10 var height: Int = 10 Size) { print(self.width, self.height) } }Copy the code