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(©OfStep)
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 to
const Pointee *
- UnsafeMutablePointer < Pointee > : similar to
Pointee *
- UnsafeRawPointer: similar to
const void *
- UnsafeMutableRawPointer: similar to
void *
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
NSArray
Pointer 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