专注iOS开发的小渣渣swift

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

2020-12-23  本文已影响0人  Henry________
图一
如果在lldb中需要获取值类型的地址,直接使用po、p、v都是无法获取地址的,只能转为指针后才可以获取,如图一。

指针

Swift的指针分类两类:

  1. typed pointer指定类型指针:unsafePointer<T>,unsafeMutablePointer<T>
  2. raw pointer未指定类型指针:unsafeRawPointer,unsafeMutableRawPointer

Swift指针与OC指针类比

Swift OC
unsafePointer<T> const T * 指定类型指针与指针内存都不可变
unsafeMutablePointer<T> T * 指定类型指针与指针内存都可变
unsafeRawPointer const void * 未知类型指针与指针内存都不可变
unsafeMutableRawPointer void * 未知类型指针与指针内存都可变

1. 未指定类型指针(raw pointer)

实例:

//获取Int的内存大小 : 8
let alignment = MemoryLayout<Int>.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()
输出结果

2. 指定类型指针(type pointer)

实例一:

var age : Int = 18
//使用值类型创建type pointer
let typePtr = withUnsafePointer(to: &age){$0}
//获取当前指针的值
print(typePtr.pointee)
输出结果

实例二:

var age : Int = 18
//创建、获取可变类型指针
let typeMutablePtr = withUnsafeMutablePointer(to: &age) {ptr -> UnsafeMutablePointer<Int> in
    //可变指针的运算
    ptr.pointee += 10
    return ptr
}
print(age)
print(typeMutablePtr.pointee)
输出结果

实例三

//初始化
let ptr = UnsafeMutablePointer<Int>.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()
输出结果

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<T> : 任意类型的托管类;是对CoreFoundation类型 T的封装,相当于OC是__bridge
 passRetained: 转换后需要持有,增加引用计数
 passUnretained: 转换后不持有,不增加引用计数
 toOpaque:将托管类转为指针(不安全)
 */
let heapPtr = Unmanaged.passUnretained(cls as AnyObject).toOpaque()
/**
 bindMemory: 将 UnsafeMutableRawPointer 指针类型更改为 UnsafeMutablePointer<Hr_HeapObject>
 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)")
输出结果
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<TestStruct>.offset 获取对象指定变量的内存偏移值
     */
    let age = UnsafeRawPointer(ptr) + MemoryLayout<TestStruct>.offset(of: \TestStruct.age)!
    //age在内存中已经标记为Int了,所以使用assumingMemoryBound
    testPointeFunc(age.assumingMemoryBound(to: Int.self))
    
    let phone = UnsafeRawPointer(ptr) + MemoryLayout<TestStruct>.offset(of: \TestStruct.phone)!
    testPointeFunc(phone.assumingMemoryBound(to: Int.self))
}

func testPointeFunc(_ p:UnsafePointer<Int>) {
    print(p.pointee)
}
输出结果

应用三: 变量的指针类型转化

var tempAge = 18
func tempAgeFunc(_ p: UnsafePointer<Int64>) {
    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只在当前作用域有效;
上一篇下一篇

猜你喜欢

热点阅读