Memory management

The basic concept

Like OC, Swift adopts ARC memory management scheme (for heap space) based on reference counting

There are three references in Swift’s ARC

Strong reference: By default, references are strong references

class Person { }
var po: Person?
Copy the code

Weak reference: Weak reference is defined by weak

class Person { }
weak var po: Person?
Copy the code

It must be an optional var because ARC automatically sets weak references to nil after instance destruction

The property viewer is not triggered when ARC automatically sets nil for weak references

Unprimary reference (unowned reference) : Define an unprimary reference by unowned

Strong references are not generated, and the memory address of the instance remains after destruction (similar to unsafe_unretained in OC).

class Person { }
unowned var po: Person?
Copy the code

Attempting to access an unowned reference after instance destruction generates a runtime error (wild pointer)

Weak, unowned usage restrictions

Weak and unowned can only be used for class instances

Only classes are stored in the heap space, and the memory of the heap space needs to be managed manually

protocol Liveable: AnyObject { }
class Person { }

weak var po: Person?
weak var p1: AnyObject?
weak var p2: Liveable?

unowned var p10: Person?
unowned var p11: AnyObject?
unowned var p12: Liveable?
Copy the code

Autoreleasepool

class Person {
    var age: Int
    var name: String
    
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
    
    func run() {}
}

autoreleasepool {
    let p = Person(age: 20, name: "Jack")
    p.run()
}
Copy the code

Reference Cycle

Weak and unowned can solve the problem of circular reference, while unowned has less performance consumption than weak

Use weak that may become nil during its lifetime

Use unowned to initialize assignments that never become nil again

Circular references to closures

Closure expressions by default generate extra strong references to the outer objects used (retain the outer objects)

The following code generates a circular reference, which causes the Person object to be unable to be freed.

class Person {
    var fn: (() -> ())?
    func run() { print("run") }
    deinit { print("deinit") }
}

func test() {
    let p = Person()
    p.fn = { p.run() }
}

test()
Copy the code

Declare weak or unowned references in the capture list of the closure expression to solve the circular reference problem

func test() { let p = Person() p.fn = { [weak p] in p? .run() } }Copy the code
func test() {
    let p = Person()
    p.fn = {
        [unowned p] in
        p.run()
    }
}
Copy the code

If you want to reference self while defining a closure attribute, the closure must be lazy (because self cannot be referenced until the instance is initialized).

class Person { lazy var fn: (() -> ()) = { [weak self] in self? .run() } func run() { print("run") } deinit { print("deinit") } }Copy the code

If an instance member (attribute, method) is used inside the closure FN, the compiler forces self to be written explicitly

If lazy is the result of a closure call, then you don’t have to worry about circular references (because the closure’s life cycle ends after the closure is called).

class Person {
    var age: Int = 0
    lazy var getAge: Int = {
        self.age
    }()

    deinit { print("deinit") }
}
Copy the code

@escaping

Non-escape closures, escape closures, are generally passed to functions as arguments

Non-escaping closures: Closure calls occur before the end of a function, and the closure calls are in function scope

typealias Fn = () -> ()

func test1(_ fn: Fn) { fn() }
Copy the code

Escaping closure: It is possible for a closure to be called after a function ends. Closure calls that escape the scope of a function are required with the @escaping declaration

typealias Fn = () -> ()

var gFn: Fn?
func test2(_ fn: @escaping Fn) { gFn = fn }
Copy the code

Dispatchqueue.global ().async is also an escape closure

An example is as follows:

import Dispatch
typealias Fn = () -> ()

func test3(_ fn: @escaping Fn) {
    DispatchQueue.global().async {
        fn()
    }
}
Copy the code
Class Person {var fn: fn // fn is an escape closure @escaping Fn) {self.fn = Fn} func run() {// dispatchqueue.global ().async is also an escape closure that uses instance members (attributes, methods), The compiler will force you to explicitly write self dispatchqueue.global ().async {self.fn()}}}Copy the code

Escape closures cannot capture inout parameters

Look at the following example

If the escape closure captures the address value of an external local variable, then the escape closure will be executed after the local variable no longer exists, and the captured value is not reasonable

Non-escape closures are guaranteed to execute closures without the end of the local variable’s life cycle

Conflicting Access to Memory

Memory access conflicts occur when two accesses satisfy the following conditions:

  • At least one is a write operation
  • They access the same memory
  • Their access times overlap (e.g. within the same function)

1. Look at the following example to see which causes memory access conflicts

func plus(_ num: inout Int) -> Int { num + 1 }

var number = 1
number = plus(&number)
Copy the code
var step = 1
func increment(_ num: inout Int) { num += step }
increment(&step)
Copy the code

The first one causes no memory access conflicts, and the second one causes memory access conflicts and an error is reported

In num += step, both the value of step is accessed and a write is performed

The solution is as follows

var step = 1
func increment(_ num: inout Int) { num += step }

var copyOfStep = step
increment(&copyOfStep)
step = copyOfStep
Copy the code

2. Look at the following examples to see which causes memory access conflicts

func balance(_ x: inout Int, _ y: inout Int) {
    let sum = x + y
    x = sum / 2
    y = sum - x
}

var num1 = 42
var num2 = 30
balance(&num1, &num2) // ok
balance(&num1, &num1) // Error
Copy the code

The first statement does not return an error because the address values of the two variables are passed in and do not conflict

The address value of the variable is the same as the address value of the variable, and num1 is read and written at the same time

And you don’t even have to run it, the compiler just reports an error

3. Look at the following examples to see which causes memory access conflicts

struct Player {
    var name: String
    var health: Int
    var energy: Int
    
    mutating func shareHealth(with teammate: inout Player) {
        balance(&teammate.health, &health)
    }
}

var oscar = Player(name: "Oscar", health: 10, energy: 10)
var maria = Player(name: "Maria", health: 5, energy: 10)
oscar.shareHealth(with: &maria)
oscar.shareHealth(with: &oscar)
Copy the code

The first sentence is executed without error, and the second sentence is executed without error

Because the address passed in is the same, it will cause memory access conflicts, and also directly at compile time error

4. Look at the following examples to see which causes memory access conflicts

var tuple = (health: 10, energy: 20)

balance(&tuple.health, &tuple.energy)

var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy)
Copy the code

Both of these errors are reported because the same storage space is being accessed at the same time

If the following conditions can be met, the properties of the overlapping access structure are safe

  • You only access instance store properties, not computed properties or class properties
  • Structs are local variables, not global variables
  • A structure is either not captured by a closure or is only captured by a non-escape closure
func test() {
    var tuple = (health: 10, energy: 20)
    balance(&tuple.health, &tuple.energy)
    
    var holly = Player(name: "Holly", health: 10, energy: 10)
    balance(&holly.health, &holly.energy)
}

test()
Copy the code

Pointer to the

Swift also has its own special pointer types, all of which are characterized as “Unsafe,” and the following four are common

  • UnsafePointer < Pointee > : similar toconst Pointee *
  • UnsafeMutablePointer < Pointee > : similar toPointee *
  • UnsafeRawPointer: similar toconst void *
  • UnsafeMutableRawPointer: similar tovoid *

UnsafePointer, UnsafeMutablePointer

var age = 10

func test1(_ ptr: UnsafeMutablePointer<Int>) {
    ptr.pointee += 10
}

func test2(_ ptr: UnsafePointer<Int>) {
    print(ptr.pointee)
}

test1(&age)
test2(&age) // 20
print(age) // 20

Copy the code

UnsafeRawPointer, UnsafeMutableRawPointer

var age = 10 

func test3(_ ptr: UnsafeMutableRawPointer) {
    ptr.storeBytes(of: 30, as: Int.self)
}

func test4(_ ptr: UnsafeRawPointer) {
    print(ptr.load(as: Int.self))
}

test3(&age)
test4(&age) // 30
print(age) // 30
Copy the code

NSArrayPointer types are also used in the traversal method of

var arr = NSArray(objects: 11, 22, 33, 44) arr.enumerateObjects { (obj, idx, stop) in print(idx, Obj) if independence idx = = 2 {/ / subscript 2 stops traversal stop pointee = true} print (" -- ")} / / / / 0 11-22 / /, / / / 1/2 / / - 33Copy the code

Stop in arr. EnumerateObjects is not the same as break. Once stop is set, execution of the code in scope will continue before the next loop is determined

Traversing elements in Swift is more suitable for enumerated

var arr = NSArray(objects: 11, 22, 33, 44)
for (idx, obj) in arr.enumerated() {
    print(idx, obj)
    if idx == 2 { break }
}
Copy the code

Gets a pointer to a variable

We can call withUnsafeMutablePointer, withUnsafePointer to get a pointer to a variable

var age = 11
var ptr1 = withUnsafeMutablePointer(to: &age) { $0 }
var ptr2 = withUnsafePointer(to: &age) { $0 }
ptr1.pointee = 22

print(ptr2.pointee) // 22
print(age) // 22

var ptr3 = withUnsafeMutablePointer(to: &age) { UnsafeMutableRawPointer($0)}
var ptr4 = withUnsafePointer(to: &age) { UnsafeRawPointer($0) }
ptr3.storeBytes(of: 33, as: Int.self)

print(ptr4.load(as: Int.self)) // 33
print(age) // 33
Copy the code

The implementation of withUnsafeMutablePointer essentially puts the variable’s address value passed in as the return value in the closure expression

func withUnsafeMutablePointer<Result, T>(to value: inout T, _ body: (UnsafeMutablePointer<T>) throws -> Result) rethrows -> Result {
    try body(&value)
}
Copy the code

Gets a pointer to the heapspace instance

Class Person {} var Person = Person() var PTR = withUnsafePointer(to: &person) {UnsafeRawPointer($0)} var heapPtr = UnsafeRawPointer(bitPattern: ptr.load(as: UInt.self)) print(heapPtr!)Copy the code

Create a pointer

The first way

var ptr = UnsafeRawPointer(bitPattern: 0x100001234)
Copy the code

The second way

Var PTR = malloc(16); .storeBytes(of: 11, as: Int.self) ptr? .storeBytes(of: 22, toByteOffset: 8, as: int.self) // Print (PTR? .load(as: Int.self)) // 11 print(ptr? .load(fromByteOffset: 8, as: int.self)) // 22 //Copy the code

The third way

Var PTR = UnsafeMutableRawPointer. The allocate (byteCount: 16, alignment: 1) / / 8 bytes stored before 11 PTR. StoreBytes (of: 11, as: Advanced (by: 8).storeBytes(of: 22, as: int. self) print(ptr.load(as: Int.self)) // 11 print(ptr.advanced(by: 8).load(as: Int.self)) // 22 ptr.deallocate()Copy the code

The fourth way

Var PTR = UnsafeMutablePointer<Int>. Allocate (capacity: 3) Succeeded ().forgoer ().forgoer ().forgoer ().forgoer ().forgoer (to).forgoer (to).forgoer (to).forgoer (to).forgoer (to).forgoer (to) 33) print(PTR. Pointee) // 11 // PTR + 1, Print ((PTR + 1).pointee) // 22 print((PTR + 2).pointee) // 33 print(PTR [0]) // 11 print(PTR [1]) // 22 Print (PTR [2]) PTR. Deinitialize (count: 3) PTR. Deallocate ()Copy the code
class Person {
    var age: Int
    var name: String
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
    
    deinit {
        print(name, "deinit")
    }
}

var ptr = UnsafeMutablePointer<Person>.allocate(capacity: 3)
ptr.initialize(to: Person(age: 10, name: "Jack"))
(ptr + 1).initialize(to: Person(age: 11, name: "Rose"))
(ptr + 2).initialize(to: Person(age: 12, name: "Kate"))

ptr.deinitialize(count: 3)
ptr.deallocate()
Copy the code

Conversion between Pointers

Var PTR = UnsafeMutableRawPointer. The allocate (byteCount: 16, alignment: 1) the imaginary a type PTR. / / assumingMemoryBound (to: Int.self) // Pointer of an indefinite type is true plus 8 bytes, different from typed pointer (PTR +8). Sumingmemorybound (to: Double. Self). Pointee = 22.0 // Cast to Int print(unsafeBitCast(PTR, to: UnsafePointer<Int>.self).pointee) // 11 print(unsafeBitCast((ptr + 8), to: UnsafePointer < Double >. The self). Pointee) / / 22.0 PTR. Deallocate ()Copy the code

UnsafeBitCast is a cast that ignores the data type and does not alter the original memory data due to a change in the data type, so this cast is also unsafe

Similar to reinterpret_cast in C++

We can use the cast pointer type of unsafeBitCast to directly copy the heapspace address stored in the Person variable into the PTR pointer variable. Since PTR is a pointer, the address it points to is the heapspace address

class Person {}
var person = Person()
var ptr = unsafeBitCast(person, to: UnsafeRawPointer.self)
print(ptr)
Copy the code

Alternatively, we can convert to a variable of type UInt and then retrieve the stored address value from the variable

class Person {}
var person = Person()
var address = unsafeBitCast(person, to: UInt.self)
var ptr = UnsafeRawPointer(bitPattern: address)
Copy the code

Look at the following example

The memory structure of Int and Double should be different, but the memory structure of age3 converted by unsafeBitCast is the same as age1, so unsafeBitCast only converts data types and does not change memory data