swift底层探索 06 - 指针简单使用

图一

如果在lldb中需要获取值类型的地址,直接使用po、p、v都是无法获取地址的,只能转为指针后才可以获取,如图一。

指针

Swift的指针分类两类:

  1. typed pointer指定类型指针:unsafePointer,unsafeMutablePointer
  2. 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由于给定了类型只需要给定移动的步数不需要给定步长.
  • initializedeinitialize是成对的

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”

【总结】

  1. 指针的内存是需要开发者手动管理的,有init/alloc一定会有dealloc
  2. 指针的优势是灵活,可以在一个首地址后添加任意类型的变量
  3. bindMemory: 更改内存绑定的类型,如果之前没有绑定那么就是首次绑定如果绑定过了,就是重新绑定类型。将指针的类型进行强制转换
  4. assumingMemoryBound: 假定内存绑定,目的是告诉编译器不需要检查memory绑定,达到混淆的目的;
  5. withMemoryRebound: 与bindMemory类似都是对指针进行类型绑定,不同的是withMemoryRebound只在当前作用域有效;

你可能感兴趣的:(swift底层探索 06 - 指针简单使用)