美文网首页专注iOS开发的小渣渣swift
swift底层探索 06 - 指针简单使用

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

作者: Henry________ | 来源:发表于2020-12-23 16:43 被阅读0次
    图一
    如果在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()
    
    输出结果
    • 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<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()
    
    输出结果
    • 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<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)")
    
    输出结果
    • HeapObject就是swift的类的结构体。在swift底层探索 01 - 类初始化&类结构一文中通过源码来推测了HeapObject以及HeapMetadata的结构,在本文中做了验证.
    • Unmanaged托管类
    • 这部分使用了passUnretained不对指针进行持有,所以不需要进行内存的管理。
    • bindMemory: 将 UnsafeMutableRawPointer 指针类型更改为 UnsafeMutablePointer<Hr_HeapObject>
    • 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<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只在当前作用域有效;

    相关文章

      网友评论

        本文标题:swift底层探索 06 - 指针简单使用

        本文链接:https://www.haomeiwen.com/subject/ztwsnktx.html