Swift 指针 & 内存管理
目录一、指针1.1 原生指针1.2 泛型指针1.3 指针应用1.3.1 获得某个变量的指针1.3.2 获取指向堆空间实例的指针1.3.3 创建指针1.3.4 指针之间的转换
二、内存管理2.1 内存访问冲突
三、循环引用3.1 闭包的循环引用
目录
一、指针
Swift 指针的不安全性:
可以指向已经释放的内存区域(内存区域已经被释放),会导致未定义的风险数组越界,⽐如创建⼀个⼤⼩为10的数组,如果通过指针访问到了 index = 11 的位置,这个时候是不是就越界了,访问了⼀个未知的内存空间。类型不一致,指针类型与内存的值类型不⼀致,也是不安全的。
指针类型:
typed pointer 指定数据类型指针(泛型指针)。raw pointer 未指定数据类型的指针(原⽣指针)。
SwiftObjctive-C备注UnsafePointerconst Pointee *指针可变,指向的内容不可变UnsafeMutablePointerPointee *指针及指向的内容都可变UnsafeRawPointerconst void *指针指向的内存区域未定UnsafeMutableRawPointervoid *指针指向的一块连续的内存区域UnsafeBufferPointerconst Pointee *指针指向的一块连续的内存区域UnsafeMutableBufferPointerPointee *指针指向的一块连续可变的内存区域UnsafeRawBufferPointerconst void *指针指向的一块连续的内存区域UnsafeMutableRawBufferPointervoid *指针指向的一块连续可变的内存区域
1.1 原生指针
原⽣指针是指当前指针未绑定到了具体的类型。
// 指针操作
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
print(MemoryLayout
print(MemoryLayout
print(MemoryLayout
for i in 0...4 {
p.advanced(by: MemoryLayout
}
for i in 0...4 {
let value = p.load(fromByteOffset: i * 8, as: Int.self)
print("\(i) - \(value)")
}
1.2 泛型指针
泛型指针是指当前指针已经绑定到了具体的类型。
// 通过指针属性 pointee 修改值
var age: Int = 18
age = withUnsafePointer(to: &age) { ptr in
print(ptr.pointee + 12)
return ptr.pointee
}
print(age)
// 通过指针属性 pointee 修改值
let ptr = UnsafeMutablePointer
ptr.initialize(to: age)
print(ptr.pointee)
struct JHStruct {
var age: Int
var name: String
}
var jhPtr = UnsafeMutablePointer
// 方式一:初始化
jhPtr[0] = JHStruct.init(age: 10, name: "张三")
jhPtr[1] = JHStruct.init(age: 10, name: "李四")
// 方式二:初始化
// jhPtr.initialize(to: JHStruct.init(age: 10, name: "张三"))
// jhPtr.advanced(by: MemoryLayout
jhPtr.deinitialize(count: 2)
jhPtr.deallocate()
1.3 指针应用
1.3.1 获得某个变量的指针
int age = 10
// 获取实例指针
var ptr = withUnsafePointer(to: &age) { $0 }
var ptr2 = withUnsafeMutablePointer(to: &age) { $0 }
1.3.2 获取指向堆空间实例的指针
// 获取指向堆空间实例指针
class Person {
var age: Int
init(age: Int) {
self.age = age
}
}
var person = Person(age: 21)
// 获取 person 指针
var ptr1 = withUnsafePointer(to: &person) { UnsafeRawPointer($0) }
// 获取 person 指针存储的地址值
var personObjcAddress = ptr1.load(as: UInt.self)
// 堆空间地址值
var ptr2 = UnsafeMutableRawPointer(bitPattern: personObjcAddress)
1.3.3 创建指针
var ptr = malloc(16)
// 存
ptr?.storeBytes(of: 10, as: Int.self)
ptr?.storeBytes(of: 20, toByteOffset: 8, as: Int.self)
// 取
ptr?load(as: Int.self)
ptr?load(fromByteOffset: 8, as: Int.self)
// 销毁
free(ptr)
// 创建指针
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
// 偏移
aPtr = ptr.advanced(by: 8)
// 销毁
ptr.deallocate()
// 创建指针
var ptr = UnsafeMutablePointer
ptr.initialize(repeating: 10, count: 3) // 用3个10去初始化
ptr.initialize(to: 10) // 用10去初始化前8个字节
// successor 后继指针
ptr.successor().initialize(to: 10)
ptr.successor().successor().initialize(to: 33)
ptr.pointee
// 这里+1,代表是偏移一个泛型字节数,如果是非泛型的,+x就是加x
(ptr+1).pointee
(ptr+2).pointee
ptr[0] // 10
ptr[1] // 10
ptr[2] // 33
// 需要反初始化,前面初始化几个,就反初始几个
ptr.deinitialize(count: 3)
// 销毁
ptr.deallocate()
1.3.4 指针之间的转换
原生指针转换为泛型指针
// 创建指针
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
// 通过assumingMemoryBound函数将非泛型指针转换为泛型指针
var ptr2 = ptr.assumingMemoryBound(to: Int.self)
// 强制转换
var ptr3 = unsafeBitCast(ptr, to: UnsafeMutablePointer
unsafeBitCast是忽略数据类型的强制转换,不会因为数据类型的变化而改变原来的内存数据
结构体转换为指针
class JHStruct {
var age: Int = 10
var name: String = "jh"
}
struct HeapObject {
var metadata: UnsafeRawPointer
var refCounted1: UInt32
var refCounted2: UInt32
}
let t = JHStruct()
// 获取对象指针
let objRawPtr = Unmanaged.passUnretained(t as AnyObject).toOpaque()
// 类型绑定
let objPtr = objRawPtr.bindMemory(to: HeapObject.self, capacity: 1)
print(objPtr.pointee)
swift 提供了三种不同的 API 来绑定/重新绑定指针
assumingMemoryBound(to:)bindMemory(to: capacity:)func testPoint(_ p: UnsafePointer
let tuple = (10, 20)
withUnsafePointer(to: tuple) { (tuplePtr: UnsafePointer<(Int, Int)>) in
// UnsafeRawPointer() 转换为原始指针,不保留指针类型
// 对于我们能明确知道指针类型,就可以告诉编译器预期的类型,让编译器绕过类型检查,并不会发生实际的转换
testPoint(UnsafeRawPointer(tuplerPtr).assumingMemoryBound(to: Int.self))
// ⽤于更改内存绑定的类型,如果当前内存还没有类型绑定,则将⾸次绑定为该类型;否则重新绑定该类型,并且内存中所有的值都会变成该类型。
testPoint(UnsafeRawPointer(tuplerPtr).bindMemory(to: Int.self, capacity: 1))
}
withMemoryRebound(to: capacity: body:)func testPointer(_ p: UnsafePointer
uint8Ptr.withMemoryRebound(to: Int8.self, capacity:count){ (int8Ptr : UnsafePointer
// 转换类型
testPointer(int8Ptr)
}
二、内存管理
跟OC一样,Swift也是使⽤⾃动引⽤计数(ARC)机制来追踪和管理内存Swift的ARC中有3中引用
强引用(strong reference):默认情况下,引用都是强引用弱引用(weak reference):通过weak定义弱引用
必须是可选类型的var,因为实例销毁后,ARC会自动将弱引用设置为nilARC自动给弱引用设置为nil时,不会出发属性观察器 无主引用(unowned reference):通过unowned定义无主引用
不会产生强应用,可以是非可选类型,实例销毁后仍然存储这实例的内存地址(类似于OC的unsafe_retained)试图在实例销毁后访问无主引用,会产生运行时错误(野指针)
weak、unowned只能用在类实例上面
// swift 实例指针结构
// Swift 引用计数存储结构(初始化函数 swift_allocObject,默认引用计数初始值 UnownedRefCount = 1,StrongExtraRefCount = 0)
HeapObject {
// 实例指针
isa
// 引用计数结构
InlineRefCounts {
atomic
// InlineRefCountBits(uint64_t): 继承 RefCountBitsT
// 无弱引用结构: Swift 引用计数由 8 字节存储,64位(0:isImmortal,1~31:UnownedRefCount(无主引用计数),32:isDeinitingMask(是否正在析构),32~62:StrongExtraRefCount(强引用计数),63:UserSlowRC)
strong RC + unowned RC + flags
OR(或者)
// 有弱引用结构: Swift 引用计数由 8 字节存储,64位(sideTable: 引用计数散列表地址,62:UserSlowRC,63:SideTableMarkShift)
HeapObjectSideTableEntry*
}
}
}
HeapObjectSideTableEntry {
// 实例指针
object pointer
// 散列表结构
SideTableRefCounts {
atomic
// SideTableRefCountBits(uint64_t + uint32_t): 继承 RefCountBitsT
strong RC + unowned RC + weak RC + flags
}
}
}
小结:⼀个对象在初始化的时候后是没有 SideTable 的,当我们创建⼀个弱引⽤的时候,系统会创建⼀个 Side Table 。
Autoreleasepool 自动释放池
public func autoreleasepool
// 自动释放池使用
autoreleasepool {
let p = MJPerson(age: 20, name: "Jack")
p.run()
}
2.1 内存访问冲突
内存访问冲突会在两个访问满足下列条件时发生:
至少一个是写入操作它们访问的是同一块内存它们的访问时间重叠(比如在同一个函数内)
// 不存在内存访问冲突
func plus(_ num: inout Int) -> Int { num + 1 }
var number = 1
number = plus(&number)
// 存在内存访问冲突
var step = 1
func increment(_ num: inout Int) -> Int { num += step }
increment(&step)
// 解决内存访问冲突
var copyStep = step
increment(&step)
step = copyStep
如果下面的条件可以满足,就说明重叠访问结构体的属性是安全的
只访问实例存储属性,不是计算属性或者类属性结构体时局部变量而非全局变量结构体要么没有被闭包捕获要么只被非逃逸闭包捕获
// ok, 局部变量,编译器认为是安全的,可控的,故不会有问题
func test() {
var tulpe = (health: 10, energy: 20)
balance(&tulpe.health, &tulpe.energy)
var holly = Player(name: "Holly", health: 10, energy: 20)
balance(&holly.health, &holly.energy)
}
test()
三、循环引用
weak、unowned 都能解决循环引用的问题,unowned 要比 weak 少一些性能消耗
在生命周期中可能会变成 nil 的使用 weak初始化赋值后再也不会变为 nil 的使用 unowned,容易出现野指针
3.1 闭包的循环引用
闭包表达式默认会对用到的外层对象产生额外的强引用(对外层对象进行了retain操作)
class Person {
var fn: (() -> ())?
func run() { print("run") }
deinit { print("deinit") }
}
func test() {
let p = Person()
p.fn = { p.run() } // 对person实例进行了retain操作,产生了循环引用
}
// 处理循环引用
func test() {
let p = Person()
p.fn = { [weak p] in // [weak p] 捕获列表
p?.run() // weak 定义的必须为可选类型
}
}
// 或者
func test() {
let p = Person()
p.fn = { [weak wp = p, unowned up = p, a = 10 + 20] in
wp?.run() // weak 定义的必须为可选类型
}
}
test()
如果想在定义闭包属性的同时引用 self,这个闭包必须是 lazy 的(因为在实例初始化完毕之后才能引用 self)
class Person {
// 提示错误,因为在实例初始化完毕后才能使用self,那这里fn在初始化过程中就使用了self,可以用lazy
var fn: (() -> ()) = {
self.run()
}
// 正确
lazy var fn: (() -> ()) = {
self.run()
}
func run() { print("run") }
deinit { print("deinit") }
}
如果 lazy属性时闭包调用的结果,那么不用考虑循环引用的问题(因为闭包调用后,闭包的生命周期就结束了)
class Person {
var age: Int = 0
// lazy 后面的()就相当于调用了闭包表达式,代表闭包的声明周期结束
lazy var getAge: Int = {
self.age
}()
deinit { print("deinit") }
}
循环引用补充知识点
// 捕获列表
var a = 10
var stu: JHStudent = JHStudent()
let closure = { [a, weak stu] in // [a] 为捕获列表参数,不可变
print(a)
// 延长生命周期: 方式一
if let strongSelf = stu {}
// 延长生命周期: 方式二
withExtendedLifetime(stu) {
// 区间内延长生命周期
}
}
相关链接
发表评论