如果在
lldb
中需要获取值类型的地址
,直接使用po、p、v
都是无法获取地址的,只能转为指针
后才可以获取,如图一。
指针
Swift的指针分类两类:
-
typed pointer
指定类型指针:unsafePointer
,unsafeMutablePointer -
raw pointer
未指定类型指针:unsafeRawPointer,unsafeMutableRawPointer
Swift指针与OC指针类比
Swift | OC | |
---|---|---|
unsafePointer |
const T * | 指定类型指针与指针内存都不可变 |
unsafeMutablePointer |
T * | 指定类型指针与指针内存都可变 |
unsafeRawPointer | const void * | 未知类型指针与指针内存都不可变 |
unsafeMutableRawPointer | void * | 未知类型指针与指针内存都可变 |
1. 未指定类型指针(raw pointer)
实例:
//获取Int的内存大小 : 8
let alignment = MemoryLayout.stride
//初始化 32字节的内存空间
//let只限制当前指针不允许更换指向,并不能限制其指向内存的修改
let rawPtr = UnsafeMutableRawPointer.allocate(byteCount: alignment * 4, alignment: alignment)
//指针赋值
for i in 0...3{
// 指针向前移动
let tempPtr = rawPtr.advanced(by: i * alignment)
// 赋值
tempPtr.storeBytes(of: i, as: Int.self)
// 赋值另一个综合API
// rawPtr.storeBytes(of: i, toByteOffset: i * alignment, as: Int.self)
}
//指针读取,每次读取都需要进行偏移
for i in 0...3{
print(rawPtr.load(fromByteOffset: i * alignment, as: Int.self))
}
//手动销毁
rawPtr.deallocate()
-
unsafe
顾名思义是不安全的,也就是从创建开始所有的内存管理都需要开发者手动管理,包括销毁
- 以上是
raw pointer
常见API - 定义指针限定符
let
只限制当前指针不允许更换指向
,并不能限制其指向内存的修改。
2. 指定类型指针(type pointer)
实例一:
var age : Int = 18
//使用值类型创建type pointer
let typePtr = withUnsafePointer(to: &age){$0}
//获取当前指针的值
print(typePtr.pointee)
-
type pointer
最简单的使用 - 在
lldb
中可以使用该方式获取值类型的指针地址
,在最开始已经有展示了。 - 当前指针是
不允许修改
的
实例二:
var age : Int = 18
//创建、获取可变类型指针
let typeMutablePtr = withUnsafeMutablePointer(to: &age) {ptr -> UnsafeMutablePointer in
//可变指针的运算
ptr.pointee += 10
return ptr
}
print(age)
print(typeMutablePtr.pointee)
- 通过
修改变量指针指向的值,来修改变量的值
实例三
//初始化
let ptr = UnsafeMutablePointer.allocate(capacity: 4)
for i in 0...3{
// 指针移动,
let tempPtr = ptr.advanced(by: i)
// 赋值
tempPtr.initialize(to: i)
}
for i in 0...3{
// 指针移动
let tempPtr = ptr.advanced(by: i)
// 获取指针的值
print("方式一:\(tempPtr.pointee)")
print("方式二:\(ptr[i])")
print("方式三:\((ptr+i).pointee)")
}
//下面两个成对出现,内存销毁
ptr.deinitialize(count: 4)
ptr.deallocate()
-
type pointer
相比raw pointer
都需要advanced
指针移动,但是不同的是位移的参数定义不一样,type pointer
由于给定了类型只需要给定移动的步数
,不需要给定步长
. -
initialize
与deinitialize
是成对的
3. 应用
应用一:实例对象绑定其他类型指针
struct Hr_HeapObject {
var kind : UnsafeRawPointer
var strongRef: UInt32
var unownedRef: UInt32
var age: Int
}
class clsModel {
var age:Int = 18
}
var cls = clsModel()
HeapObject
将cls
的内存布局绑定到Hr_HeapObject
中.
/**
Unmanaged : 任意类型的托管类;是对CoreFoundation类型 T的封装,相当于OC是__bridge
passRetained: 转换后需要持有,增加引用计数
passUnretained: 转换后不持有,不增加引用计数
toOpaque:将托管类转为指针(不安全)
*/
let heapPtr = Unmanaged.passUnretained(cls as AnyObject).toOpaque()
/**
bindMemory: 将 UnsafeMutableRawPointer 指针类型更改为 UnsafeMutablePointer
assumingMemoryBound: 若当前指针已经在内存中进行过类型绑定,则使用assumingMemoryBound做假定内存绑定。
目的是告诉编译器不需要检查memory绑定
*/
let metaPtr = heapPtr.bindMemory(to: Hr_HeapObject.self, capacity: 1)
//输出
print("kind:\(metaPtr.pointee.kind)")
print("strongRef:\(metaPtr.pointee.strongRef)")
print("unownedRef:\(metaPtr.pointee.unownedRef)")
print("age:\(metaPtr.pointee.age)")
-
HeapObject
就是swift的类的结构体。在swift底层探索 01 - 类初始化&类结构一文中通过源码来推测了HeapObject
以及HeapMetadata
的结构,在本文中做了验证. - Unmanaged托管类
- 这部分使用了
passUnretained
不对指针进行持有,所以不需要进行内存的管理。 -
bindMemory
: 将 UnsafeMutableRawPointer 指针类型更改为 UnsafeMutablePointer -
assumingMemoryBound
:若当前指针已经在内存中进行过类型绑定
,则使用assumingMemoryBound做假定内存绑定;目的是告诉编译器不需要检查memory绑定
HeapMetaData
//按照上文的逻辑和OC的逻辑,kind指针指向的是类的`元类`
struct hr_HeapMetaData {
var kind: UnsafeRawPointer
var superClass: UnsafeRawPointer
var cachedata1: UnsafeRawPointer
var cachedata2: UnsafeRawPointer
var data: UnsafeRawPointer
var flags: UInt32
var instanceAddressOffset: UInt32
var instanceSize: UInt32
var flinstanceAlignMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressOffset: UInt32
var description: UnsafeRawPointer
}
//对kind的执行进行重新绑定
let clsPtr = metaPtr.pointee.kind.bindMemory(to: hr_HeapMetaData.self, capacity: 1)
print(clsPtr.pointee)
应用二:获取结构体属性的指针
struct TestStruct {
var age:Int = 18
var phone:Int = 1888888888
}
//初始化
var testStr = TestStruct()
//type pointer转换
withUnsafePointer(to: &testStr) { (ptr) in
//内部ptr是个read-only所以无法继续进行 type pointer转换
/**
MemoryLayout.offset 获取对象指定变量的内存偏移值
*/
let age = UnsafeRawPointer(ptr) + MemoryLayout.offset(of: \TestStruct.age)!
//age在内存中已经标记为Int了,所以使用assumingMemoryBound
testPointeFunc(age.assumingMemoryBound(to: Int.self))
let phone = UnsafeRawPointer(ptr) + MemoryLayout.offset(of: \TestStruct.phone)!
testPointeFunc(phone.assumingMemoryBound(to: Int.self))
}
func testPointeFunc(_ p:UnsafePointer) {
print(p.pointee)
}
应用三: 变量的指针类型转化
var tempAge = 18
func tempAgeFunc(_ p: UnsafePointer) {
print(p.pointee)
}
//直接调用类型不同会报错
//tempAgeFunc(tempAge)
// 获取指针地址
withUnsafePointer(to: &tempAge) { (ptr) in
// 1. 将当前指针的类型进行转换
// 2. 对未知类型指针进行类型绑定
let temp = UnsafeRawPointer(ptr).bindMemory(to: Int64.self, capacity: 1)
tempAgeFunc(temp)
}
方法二:
let tempPtr = withUnsafePointer(to: &tempAge) {$0 }
//对withUnsafePointer中的值临时进行修改,只在该作用域中有效,更加常用
tempPtr.withMemoryRebound(to: Int64.self, capacity: 1) { (ptr) in
tempAgeFunc(ptr)
}
指针类型可以随意转换
unsafeBitCast
unsafeBitCast
是非常危险的操作,它会将一个指针指向的内存强制按位转换为目标的类型。因为这种转换是在 Swift 的类型管理之外进行的,因此编译器无法确保得到的类型是否确实正确,你必须明确地知道你在做什么。比如:
let arr = NSArray(object: "meow")
let str = unsafeBitCast(CFArrayGetValueAtIndex(arr, 0), to: CFString.self)
str // “meow”
【总结】
-
指针
的内存是需要开发者手动管理的,有init/alloc
一定会有dealloc
-
指针
的优势是灵活
,可以在一个首地址后添加任意类型的变量
; -
bindMemory
: 更改内存绑定的类型,如果之前没有绑定那么就是首次绑定
如果绑定过了,就是重新绑定类型
。将指针的类型进行强制转换
; -
assumingMemoryBound
:假定内存绑定
,目的是告诉编译器不需要检查memory绑定,达到混淆的目的; -
withMemoryRebound
: 与bindMemory
类似都是对指针进行类型绑定,不同的是withMemoryRebound
只在当前作用域有效;