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