# 内存管理 ## 弱引用 Swift 和 OC 都是通过引用计数方式来管理内存的。 Swift 的 ARC 存在3种情况: - 强引用(strong reference)。默认情况下,都是强引用。 当一个强指针离开作用域后,会自动释放对象,调用 deinit 方法。 ```swift class Person { deinit { print("Person deinit") } } func test () { let p: Person = Person() } print("1") test() print("2") // console 1 Person deinit 2 ``` - 弱引用(weak reference)。通过 weak 定义弱引用。**必须是可选类型,因为实例销毁后,ARC 会自动将弱引用设置为 nil**。 ```swift weak var p: Person? = Person() ``` - 弱引用如果被设置为 nil,是不会触发属性观察器的 willSet、didSet 方法的 ```swift class Dog { deinit { print("Dog deinit") } } class Person { weak var dog: Dog? { willSet { print("willSet") } didSet { print("didSet") } } deinit { print("Person deinit") } } func test () { let p: Person = Person() p.dog = Dog() print(p) } print("1") test() print("2") // console 1 willSet // 这里的触发是 test 方法里,给 person 对象设置了 dog 属性时触发的。但是 weak 指针设置为 nil 的时候没有触发属性观察器 didSet Dog deinit SwiftDemo.Person Person deinit 2 ``` 换一种写法。可以发现在 init 方法里面,属性观察器 willSet、didSet 是不会触发的。 ```swift class Dog { deinit { print("Dog deinit") } } class Person { weak var dog: Dog? { willSet { print("willSet") } didSet { print("didSet") } } init (dog: Dog?) { self.dog = dog } deinit { print("Person deinit") } } func test () { let p: Person = Person(dog: Dog()) print(p) } print("1") test() print("2") // console 1 Dog deinit SwiftDemo.Person Person deinit 2 ``` - 无主引用(unowned reference)。通过 unowned 定义无主引用 - 不会产生强引用,非可选类型。实例销毁后仍然存储着实例的内存地址,类似 OC 的 `unsafe_retained` - 如果在实例销毁后访问无主引用,会产生野指针错误 weak、unowned 只能用在类实例上。比如: ```swift protocol Liveavle: AnyObject { } class Person { } weak var p1: Person? weak var p2: AnyObject? weak var p3: Liveavle? unowned var p4: Person? unowned var p5: AnyObject? unowned var p6: Liveavle? ``` ## 循环引用 weak、unowned 都能解决循环引用问题。但是 weak 由于当对象释放后,会把指针设置为 nil。所以 unowned 会比 weak 的性能更好。 - 在生命周期中对象可能会变为 nil,推荐使用 weak - 初始化赋值后再也不会变为 nil 的对象,推荐使用 unowned ## 闭包的循环引用 上面的代码会发生循环引用,会导致局部变量的 p 无法释放(看不到 Person 的 deinit 方法调用) 解法: - **在闭包表达式的捕获列表声明 weak 或者 unowned 引用,解决循环引用的问题** 因为在闭包里,声明的捕获列表中将 p 用 weak 修饰,所以可以为 nil。p 使用到的地方必须用 `p?.run()` ```swift class Person { var fn:(() -> ())? func run () { print("run") } deinit { print("deinit") } } func test () { let p = Person() p.fn = { [weak p] in p?.run() } } test() // deinit ``` 另一种写法 ```swift p.fn = { [unowned p] in p.run() } ``` - ```swift class Person { lazy var fn:() -> () = { self.run() } func run () { print("run") } deinit { print("deinit") } } ``` ## @escaping - 非逃逸闭包、逃逸闭包,一般都是当作参数传递给函数 - 非逃逸闭包:闭包调用发生在函数结束前,闭包调用在函数作用域内 - 逃逸闭包:闭包油可能在函数结束后调用, 闭包调用逃离了函数的作用域,需要通过 `@eascaping` 声明 ```swift typealias Fn = () -> () var globalFn:Fn? func setFn(_ fn: @escaping Fn) { globalFn = fn } setFn { print("Hello world") } globalFn?() // Hello world ``` 注意点:逃逸闭包不可以捕获 `inout` 参数 ```swift typealias Fn = () -> () func other1(_ fn: Fn) { fn() } func other2(_ fn: @escaping Fn) { fn() } func test(value: inout Int) -> Fn { other1 { value += 1 } other2 { // compile error:Escaping closure captures 'inout' parameter 'value' value += 1 } func add() { value += 1 } return add // compile error:Escaping closure captures 'inout' parameter 'value' } ``` 原因:因为 `inout` 参数的本质是要求函数在调用期间直接操作变量的内存地址,而逃逸闭包可能会在函数返回后的任何时刻调用(不确定),这时 `inout` 参数所在的内存地址可能已经不再有效或者已经被其他值覆盖。因此,允许逃逸闭包捕获 `inout` 参数会导致潜在的数据不一致和安全问题。 ## 内存访问冲突 Confilicting Access to Memory, 内存访问冲突发生在: - 至少一个是写入操作 - 它们访问的是同一块内存 - 它们的访问时间重叠(比如在同一个函数内) Demo1: ```swift var step = 1 func increament(_ num: inout Int) { num += step } increament(&step) ``` 解决办法就是打破3个条件之一。显然不可以换函数,只有改变「同时访问一块内存地址」这个条件了 ```swift var step = 1 func increament(_ num: inout Int) { num += step } var stepCopy = step increament(&stepCopy) step = stepCopy ``` Demo2: ```swift func balance(_ x: inout Int, _ y: inout Int) { let sum = x + y x = sum/2 y = sum - x } var num1 = 1 var num2 = 2 balance(&num1, &num2) // balance(&num1, &num1) // compile error: Inout arguments are not allowed to alias each other ``` Demo3: 下面代码虽然看着传入的是不同内存地址,但是 health 和 power 都属于元祖,还是同一个内存地址。 如何解决? Swift 规定以下 case,就说明重叠访问结构体的属性就是安全的 - 只访问实例存储属性,不是计算属性或者类属性 - 结构体是局部变量而非全局变量 - 结构体要么没有被闭包捕获,要么只被非逃逸闭包捕获 ```swift func balance(_ x: inout Int, _ y: inout Int) { let sum = x + y x = sum/2 y = sum - x } func testConflictingAccessToMemory() { var tumple = (health: 100, power: 100) balance(&tumple.health, &tumple.power) } testConflictingAccessToMemory() ``` ## 指针 Swift 也有专门的指针类型,都被定义为 Unsafe(不安全的),有: - `UnsafePointer` 类似于 `const Person *` - `UnsafeMutablePointer` 类似于 `Person *` - `UnsafeRawPonter` 类似于 `const void *` - `UnsafeMutableRawPonter` 类似于 `void *` Demo1 因为 changeValue1 的参数是不可变的指针,所以方法内部去修改值,编译器会报错。 ```swift var age = 27 func changeValue1(_ num: UnsafePointer) { num.pointee = 28 // compile error: Cannot assign to property: 'pointee' is a get-only property } func changeValue2(_ num: UnsafeMutablePointer) { num.pointee = 28 } changeValue1(&age) print(age) changeValue2(&age) print(age) ``` - `changeValue1` 的参数是不可变的指针,所以方法内部去修改值,编译器会报错 - `changeValue2` 的参数是可变的指针,所以方法内部去修改值,编译没问题 - 指针加了泛型,访问真实的值可以通过 `指针.pointee` 去访问 Demo2 ```swift func changeValue3(_ num: UnsafeRawPointer) { let value = num.load(as: Int.self) print("value is \(value)") } func changeValue4(_ num: UnsafeMutableRawPointer) { num.storeBytes(of: 30, as: Int.self) } changeValue3(&age) print(age) changeValue4(&age) print(age) // console value is 27 27 30 ``` - `changeValue3` 传递了不可变的原始指针,所以访问内存上的值,需要程序员自己指定访问的数据类型。使用 `指针.load(as: 数据类型.self)` 这种格式 - `changeValue4` 传递了可变的原始指针,所以修改内存上的值,需要程序员自己指定访问的数据类型。使用 `指针.storeBytes(of: 值, as: 数据类型.self)` 这种格式 系统使用场景 ```swift import Foundation var objcArray = NSArray(objects: 10, 11, 12, 13) objcArray.enumerateObjects { element, idx, stop in print("element is \(element) at index \(idx)") } print("--------------------") objcArray.enumerateObjects { element, idx, stop in if idx == 1 { stop.pointee = true } print("element is \(element) at index \(idx)") } element is 10 at index 0 element is 11 at index 1 element is 12 at index 2 element is 13 at index 3 -------------------- element is 10 at index 0 element is 11 at index 1 ``` tips: 不可以在数组 `enumerateObjects` 方法中使用 break,否则编译器会提示 `Unlabeled 'break' is only allowed inside a loop or switch, a labeled break is required to exit an if or do` ## 获取某个变量的指针 `withUnsafePointer` `withUnsafeMutablePointer` 可以获取到不可变、可变的指针 ```swift var age = 27 let pointer = withUnsafePointer(to: &age) { pointer in let address = UnsafeRawPointer(pointer).load(as: Int.self) print("Memory address is \(pointer), the value is \(address)") return pointer } var ptr = withUnsafePointer(to: &age) { $0 } print(ptr) // console Memory address is 0x000000010000c208, the value is 27 0x000000010000c208 0x000000010000c208 ``` 继续添加代码,利用指针修改值,编译器报错 `Cannot assign to property: 'pointee' is a get-only property` ```swift ptr.pointee = 28 // Cannot assign to property: 'pointee' is a get-only property ``` 再修改代码 ```swift var mutablePtr = withUnsafeMutablePointer(to: &age) { $0 } mutablePtr.pointee = 28 print("mutable address is \(mutablePtr), value is \(age)") // console mutable address is 0x000000010000c218, value is 28 ``` 说明: ``` let pointer = withUnsafePointer(to: &age) { pointer in return pointer } ``` 等价于下面的写法($0 是第一个参数,return 可以省略) ```swift let pointer = withUnsafePointer(to: &age) { pointer in return $0 } let pointer = withUnsafePointer(to: &age) { $0 } ``` 那如何获取不可变和可变的 rawPointer ```swift let rawPointer: UnsafeRawPointer = withUnsafePointer(to: &age) { UnsafeRawPointer($0) } print("Raw pointer address is \(rawPointer), the value is \(rawPointer.load(as: Int.self))") let mutableRawPointer: UnsafeMutableRawPointer = withUnsafeMutablePointer(to: &age) { UnsafeMutableRawPointer($0) } print("Mutable raw pointer address is \(rawPointer), the value is \(rawPointer.load(as: Int.self))") mutableRawPointer.storeBytes(of: 28, as: Int.self) print(age) // console Raw pointer address is 0x000000010000c218, the value is 27 Mutable raw pointer address is 0x000000010000c218, the value is 27 28 ``` pointer、pointee,英语中 er、ee,er 表示主动,ee 表示被动,分别是:指针、被指向的对象。 上述方式获取的都是指针变量的地址值,而不是堆空间对象的地址值。 ## 获取堆空间对象的指针 先获取 `UnsafeRawPointer`,然后利用 `UnsafeRawPointer(bitPattern:**)` 获取堆空间对象的地址值 ```swift class Person { var age: Int init(age: Int) { self.age = age } } var p: Person = Person(age: 27) var ptr1 = withUnsafePointer(to: p) { UnsafeRawPointer($0) } var personHeapAddress = ptr1.load(as: UInt.self) var ptr2 = UnsafeRawPointer(bitPattern: personHeapAddress) print(ptr2) print(Mems.ptr(ofRef: p)) ``` ## 创建指针 创建内存方法1: `malloc` ```swift import Foundation var ptr = malloc(16) print("malloc address is \(ptr)") // 存 ptr?.storeBytes(of: 10, as: Int.self) ptr?.storeBytes(of: 20, toByteOffset: 8, as: Int.self) let firstValue = (ptr?.load(as: Int.self))! let secondValue = (ptr?.load(fromByteOffset: 8, as: Int.self))! print("The first part is \(firstValue), second part is \(secondValue)") free(ptr) // console malloc address is Optional(0x0000600000008040) The first part is 10, second part is 20 ``` 创建内存方法2: `UnsafeMutableRawPointer.allocate(byteCount: 字节数, alignment: 内存对齐)` ```swift // 创建 let ptr: UnsafeMutableRawPointer = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1) print("malloc address is \(ptr)") ptr.storeBytes(of: 10, as: Int.self) // ptr.storeBytes(of: 20, toByteOffset: 8, as: Int.self) ptr.advanced(by: 8).storeBytes(of: 20, as: Int.self) let firstValue = ptr.load(as: Int.self) let secondValue = ptr.load(fromByteOffset: 8, as: Int.self) print("The first part is \(firstValue), second part is \(secondValue)") // 释放 ptr.deallocate() // console malloc address is 0x0000000100604370 The first part is 10, second part is 20 ``` 上面的 `ptr.storeBytes(of: 20, toByteOffset: 8, as: Int.self)` 写法等价于 `ptr.advanced(by: 8).storeBytes(of: 20, as: Int.self)` 创建内存方法3: `UnsafeMutablePointer.allocate(capacity: 2)` 创建2* 8 Byte 大小的内存 ```swift import Foundation var ptr = UnsafeMutablePointer.allocate(capacity: 2) print("malloc address is \(ptr)") // 初始化赋值 //ptr.pointee = 27 ptr.initialize(to: 27) ptr.successor().initialize(to: 10) // 访问 print(ptr.pointee) print((ptr + 1).pointee) print(ptr[0]) print(ptr[1]) print(ptr.pointee) print(ptr.successor().pointee) ptr.deinitialize(count: 2) ptr.deallocate() // console malloc address is 0x0000000100604190 27 10 27 10 27 10 ``` ## 指针之间的转换 `unsafeBitCast` 是忽略数据类型的强制转换,不会因为数据类型的变化而改变原来的内存数据。类似 C++ 中的 `reterpret_cast` ## 内存泄漏 weak 和 unowned 是两种用于处理引用循环(retain cycles)的关键字,它们主要用在类的属性中,以确保对象之间的引用不会导致内存泄漏。这两种引用类型都用于表示对另一个对象的非拥有(non-owning)引用,但它们在行为上有所不同。 weak 引用是一个不持有对象引用的引用。这意味着它不会增加对象的引用计数。当对象不再被拥有时(即其引用计数为 0),weak 引用会自动设置为 nil。 weak 引用通常用于避免循环引用,特别是在闭包或代理模式中。例如,如果你有一个视图控制器(ViewController)和一个代理(Delegate),并且 ViewController 持有一个对 Delegate 的强引用,那么为了避免循环引用,Delegate 通常会对 ViewController 持有一个 weak 引用。 unowned 引用也是一个不持有对象引用的引用,但它不会在对象被释放时自动设置为 nil。因此,使用 unowned 引用时需要格外小心,因为如果引用的对象被释放了,而你的代码仍然试图访问它,那么你的程序将会崩溃。 通常,当你确信引用的对象在其生命周期内始终存在时,才会使用 unowned 引用。例如,在一个父对象和子对象的关系中,如果父对象始终在子对象之前存在,并且子对象需要引用父对象,那么子对象可以使用 unowned 引用指向父对象。 ## inout 参数访问冲突 ``` var step = 1 func increment(_ number: inout Int) { number += step } let rs = increment(&step) print(rs) ```swift 问题本质是:`inout` 关键词代表要对函数的参数进行写操作,而函数内部的实现利用 step 进行反问操作。读写同时存在就有问题。 如何解决?产生一个备份,然后调用函数,最后将备份产生的结果覆盖老值 ``` var step = 1 func increment(_ number: inout Int) { number += step } // make an explicit copy var copyOfStep = step // invoke increment(©OfStep) // update the original value step = copyOfStep print(step) // 2 ```swift