# 内存管理
## 弱引用
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