一、指针为什么不安全
-
⽐如我们在创建⼀个对象的时候,是需要在堆分配内存空间的。但是这个内存空间的声明周期是有限的,也就意味着如果我们使⽤指针指向这块内容空间,如果当前内存空间的⽣命周期啊到了(引⽤计数为0),那么我们当前的指针是不是就变成了未定义的⾏为了。
野指针 -
我们创建的内存空间是有边界的,⽐如我们创建⼀个⼤⼩为10的数组,这个时候我们通过指针访问 到了 index = 11 的位置,这个时候是不是就
越界了,访问了⼀个未知的内存空间。 -
指针类型与内存的值
类型不⼀致,也是不安全的。如精度缺失
二、指针类型
Swift中的指针分为两类, typed pointer 指定数据类型指针, raw pointer 未指定数据类型的指针(原⽣指针)。基本上我们接触到的指针类型有⼀下⼏种
1.Raw Pointer 原始指针
利用原始指针存储int数据
1.1 MemoryLayout
let stride = MemoryLayout.stride//步长
let size = MemoryLayout.size//实际内存大小
let alignment = MemoryLayout.alignment//对齐字节数
虽然实际bool占用1字节,但是8字节对齐,所以步长是16,计算机自动对齐,用空间换时间,速度快
为什么上面存储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)
}
2.Typed Pointer 泛型指针
2.1 解释
泛型指针相⽐较原⽣指针来说,其实就是指定当前指针已经绑定到了具体的类型。
2.2 访问
进⾏泛型指针访问的过程中,我们并不是使⽤ load 和 store ⽅法来进⾏存储操作。这⾥我们使⽤ 到当前泛型指针内置的变量pointee
2.3 已有变量
通过withUnsafePointer得到变量的地址
通过指针修改变量
-
通过返回表达式修改,这里没有修改pointee -
直接修改pointee,指针要变成可变类型的指针
withUnsafeMutablePointer
2.3 直接分配内存
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)
或者
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()
三、指针绑定类型
Swift 提供了三种不同的 API 来绑定/重新绑定指针:
1.assumingMemoryBound(to:)
让编译器绕过类型检查,并没有发⽣实际类型的转换
类型不匹配报错
可以用assumingMemoryBound绕过编译器,需要先转成原始指针
2.bindMemory(to: capacity:)
⽤于更改内存绑定的类型,如果当前内存还没有类型绑定,则将⾸次绑定为该类型;否则重新绑定该类 型,并且内存中所有的值都会变成该类型。
3.withMemoryRebound(to: capacity: body:)
当我们在给外部函数传递参数时,不免会有⼀些数据类型上的差距。如果我们进⾏类型转换,必然要来回复制数据;这个时候我们就可以使⽤ withMemoryRebound(to: capacity: body:) 来临时更改内存绑定类型。
四、内存管理
引用计数
Swift 中使⽤⾃动引⽤计数(ARC)机制来追踪和管理内存。
weak弱引用下的引用计数
会创建Side Table
总结:
swift两种 RefCounts
- ⼀种是 inline 的,⽤在 HeapObject 中,它其实是⼀个 uint64_t ,可以当引⽤计数也可以当 Side Table 的指针。
- 一种是Side Table 是⼀种类名为 HeapObjectSideTableEntry 的结构,⾥⾯也有 RefCounts 成员,是 内部是 SideTableRefCountBits ,其实就是
原来的 uint64_t 加上⼀个存储弱引⽤数的 uint32_t。
循环引用
对象循环引用
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")
}
}
###闭包循环引用
互相持有 导致无法释放
可以用弱引用或者无组引用来打破循环引用
weak vs unowned
- 如果两个对象的⽣命周期完全和对⽅没关系(其中⼀⽅什么时候赋值为nil,对对⽅都没影响),请⽤ weak
- 如果你的代码能确保:其中⼀个对象销毁,另⼀个对象也要跟着销毁,这时候,可以(谨慎)⽤ unowned
weak是可选项,无组引用不是可选项,不确定怎么用那么无脑使用weak
weak
unowned
weak strong dance
-
if let
-
guard let else