swift指针(4)

277 阅读4分钟

一、指针为什么不安全

  • ⽐如我们在创建⼀个对象的时候,是需要在堆分配内存空间的。但是这个内存空间的声明周期是有限的,也就意味着如果我们使⽤指针指向这块内容空间,如果当前内存空间的⽣命周期啊到了(引⽤计数为0),那么我们当前的指针是不是就变成了未定义的⾏为了。 野指针

  • 我们创建的内存空间是有边界的,⽐如我们创建⼀个⼤⼩为10的数组,这个时候我们通过指针访问 到了 index = 11 的位置,这个时候是不是就越界了,访问了⼀个未知的内存空间

  • 指针类型与内存的值类型不⼀致,也是不安全的。如精度缺失

二、指针类型

Swift中的指针分为两类, typed pointer 指定数据类型指针, raw pointer 未指定数据类型的指针(原⽣指针)。基本上我们接触到的指针类型有⼀下⼏种

image.png

1.Raw Pointer 原始指针

利用原始指针存储int数据

image.png

1.1 MemoryLayout

let stride = MemoryLayout.stride//步长

let size = MemoryLayout.size//实际内存大小

let alignment = MemoryLayout.alignment//对齐字节数

虽然实际bool占用1字节,但是8字节对齐,所以步长是16,计算机自动对齐,用空间换时间,速度快 image.png

为什么上面存储0-1-2-3 4个int输出不对呢,是因为没有考虑到步长,每次添加之后p的步长都变化了

1.2 UnsafeMutableRawPointer.allocate创建指针 storeBytes存数据 load取数据

let stride = MemoryLayout<Int>.stride
let size = MemoryLayout<Int>.size
let alignment = MemoryLayout<Int>.alignment
print("stride:\(stride) size:\(size) alignment:\(alignment)")
let p = UnsafeMutableRawPointer.allocate(byteCount: 4*stride, alignment: alignment)
for i in 0 ..< 4 {
   p.advanced(by: i*stride).storeBytes(of: i, as: Int.self)
}
for i in 0 ..< 4 {
   let num = p.load(fromByteOffset: i*stride, as: Int.self)
   print(num)
}

image.png

2.Typed Pointer 泛型指针

2.1 解释

泛型指针相⽐较原⽣指针来说,其实就是指定当前指针已经绑定到了具体的类型。

2.2 访问

进⾏泛型指针访问的过程中,我们并不是使⽤ load 和 store ⽅法来进⾏存储操作。这⾥我们使⽤ 到当前泛型指针内置的变量pointee

2.3 已有变量

通过withUnsafePointer得到变量的地址

image.png

通过指针修改变量

  • 通过返回表达式修改,这里没有修改pointee image.png

  • 直接修改pointee,指针要变成可变类型的指针withUnsafeMutablePointer

image.png

image.png

2.3 直接分配内存

image.png

 struct NBTeacher{
    var age = 18
    var height = 180
}
        
var teacher = UnsafeMutablePointer<NBTeacher>.allocate(capacity: 2)
teacher[0] = NBTeacher.init(age: 20, height: 183)
teacher[1] = NBTeacher()
print(teacher.pointee,"\n",teacher.advanced(by: MemoryLayout<NBTeacher>.stride).pointee)

image.png

或者

initialize 和 deinitialize要成对出现

struct NBTeacher{
    var age = 18
    var height = 180
}

var teacher = UnsafeMutablePointer<NBTeacher>.allocate(capacity: 2)
teacher.initialize(to: NBTeacher.init(age: 20, height: 183))
teacher.advanced(by: MemoryLayout<NBTeacher>.stride).initialize(to: NBTeacher())
print(teacher.pointee,"\n",teacher.advanced(by: MemoryLayout<NBTeacher>.stride).pointee)

teacher.deinitialize(count: 2)
teacher.deallocate()

image.png

三、指针绑定类型

Swift 提供了三种不同的 API 来绑定/重新绑定指针:

1.assumingMemoryBound(to:)

让编译器绕过类型检查,并没有发⽣实际类型的转换

类型不匹配报错 image.png

可以用assumingMemoryBound绕过编译器,需要先转成原始指针

image.png

2.bindMemory(to: capacity:)

⽤于更改内存绑定的类型,如果当前内存还没有类型绑定,则将⾸次绑定为该类型;否则重新绑定该类 型,并且内存中所有的值都会变成该类型。

3.withMemoryRebound(to: capacity: body:)

当我们在给外部函数传递参数时,不免会有⼀些数据类型上的差距。如果我们进⾏类型转换,必然要来回复制数据;这个时候我们就可以使⽤ withMemoryRebound(to: capacity: body:) 来临时更改内存绑定类型

四、内存管理

引用计数

Swift 中使⽤⾃动引⽤计数(ARC)机制来追踪和管理内存。

image.png

weak弱引用下的引用计数

会创建Side Table

总结:

swift两种 RefCounts

  • ⼀种是 inline 的,⽤在 HeapObject 中,它其实是⼀个 uint64_t ,可以当引⽤计数也可以当 Side Table 的指针。
  • 一种是Side Table 是⼀种类名为 HeapObjectSideTableEntry 的结构,⾥⾯也有 RefCounts 成员,是 内部是 SideTableRefCountBits ,其实就是原来的 uint64_t 加上⼀个存储弱引⽤数的 uint32_t

循环引用

对象循环引用

image.png

class NBCBPerson{
    var bank:NBCBBank
    init(_ bank:NBCBBank){
        self.bank = bank
    }
    deinit{
        print("deinit NBCBPerson")
    }
}

class NBCBBank{
    deinit{
        print("deinit NBCBBank")
    }
    var person:NBCBPerson?
    static func test(){
        var bank = NBCBBank()
        bank.person = NBCBPerson(bank)
        print("end")
    }
}

image.png

###闭包循环引用

image.png

image.png 互相持有 导致无法释放

可以用弱引用或者无组引用来打破循环引用

weak vs unowned

  • 如果两个对象的⽣命周期完全和对⽅没关系(其中⼀⽅什么时候赋值为nil,对对⽅都没影响),请⽤ weak
  • 如果你的代码能确保:其中⼀个对象销毁,另⼀个对象也要跟着销毁,这时候,可以(谨慎)⽤ unowned

weak是可选项,无组引用不是可选项,不确定怎么用那么无脑使用weak

weak image.png

unowned image.png

weak strong dance

  • if let

  • guard let else