mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-25 04:17:17 +00:00
462 lines
13 KiB
Markdown
462 lines
13 KiB
Markdown
# 内存管理
|
||
|
||
|
||
|
||
## @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)
|
||
```
|
||
|
||
|
||
|
||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftConflictingAccessToMemoryDemo.png" style="zoom:25%">
|
||
|
||
解决办法就是打破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 都属于元祖,还是同一个内存地址。
|
||
|
||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftConflictingAccessToMemoryDemo2.png" style="zoom:25%">
|
||
|
||
|
||
|
||
如何解决?
|
||
|
||
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<Person>` 类似于 `const Person *`
|
||
- `UnsafeMutablePointer<Person>` 类似于 `Person *`
|
||
- `UnsafeRawPonter` 类似于 `const void *`
|
||
- `UnsafeMutableRawPonter` 类似于 `void *`
|
||
|
||
|
||
|
||
Demo1
|
||
|
||
因为 changeValue1 的参数是不可变的指针,所以方法内部去修改值,编译器会报错。
|
||
|
||
```swift
|
||
var age = 27
|
||
func changeValue1(_ num: UnsafePointer<Int>) {
|
||
num.pointee = 28 // compile error: Cannot assign to property: 'pointee' is a get-only property
|
||
}
|
||
func changeValue2(_ num: UnsafeMutablePointer<Int>) {
|
||
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<Int>.allocate(capacity: 2)` 创建2* 8 Byte 大小的内存
|
||
|
||
```swift
|
||
import Foundation
|
||
var ptr = UnsafeMutablePointer<Int>.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
|
||
|
||
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/SwiftInou tReadWriteError.png" style="zoom:25%">
|
||
|
||
问题本质是:`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 |