美文网首页Swiftswift
Swift-进阶 04:指针

Swift-进阶 04:指针

作者: Style_月月 | 来源:发表于2020-12-17 17:24 被阅读0次

    Swift 进阶之路 文章汇总

    Swift-进阶 04:指针

    本文主要介绍swift中的指针

    swift中的指针分为两类

    • typed pointer 指定数据类型指针,即 UnsafePointer<T>,其中T表示泛型

    • raw pointer 未指定数据类型的指针(原生指针) ,即UnsafeRawPointer

    swift与OC指针对比如下:

    Swift OC 说明
    unsafePointer<T> const T * 指针及所指向的内容都不可变
    unsafeMutablePointer T * 指针及其所指向的内存内容均可变
    unsafeRawPointer const void * 指针指向未知类型
    unsafeMutableRawPointer void * 指针指向未知类型

    原生指针

    原生指针:是指未指定数据类型的指针,有以下说明

    • 对于指针内存管理是需要手动管理的

    • 指针在使用完需要手动释放

    有以下一段原生指针的使用代码,请问运行时会发生什么?

    //原生指针
    //对于指针的内存管理是需要手动管理的
    //定义一个未知类型的指针:本质是分配32字节大小的空间,指定对齐方式是8字节对齐
    let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
    
    //存储
    for i in 0..<4 {
        p.storeBytes(of: i + 1, as: Int.self)
    }
    //读取
    for i in 0..<4 {
        //p是当前内存的首地址,通过内存平移来获取值
        let value = p.load(fromByteOffset: i * 8, as: Int.self)
        print("index: \(i), value: \(value)")
    }
    
    //使用完成需要dealloc,即需要手动释放
    p.deallocate()
    
    • 通过运行发现,在读取数据时有问题,原因是因为读取时指定了每次读取的大小,但是存储是直接在8字节的p中存储了i+1,即可以理解为并没有指定存储时的内存大小

      image
    • 修改:通过advanced(by:)指定存储时的步长

    //存储
    for i in 0..<4 {
        //指定当前移动的步数,即i * 8
        p.advanced(by: i * 8).storeBytes(of: i + 1, as: Int.self)
    }
    

    修改后的运行结果如下


    image

    type pointer

    在前几篇文章中,我们获取基本数据类型的地址是通过withUnsafePointer(to:)方法获取的

    • 查看withUnsafePointer(to:的定义中,第二个参数传入的是闭包表达式,然后通过rethrows重新抛出Result(即闭包表达式产生的结果)了,所以可以将闭包表达式进行简写(简写参数、返回值),其中$0表示第一个参数,$1表示第二个参数,以此类推
    <!--定义-->
    @inlinable public func withUnsafePointer<T, Result>(to value: inout T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result
    
    <!--使用1-->
    var age = 10
    let p = withUnsafePointer(to: &age) { $0 }
    print(p)
    
    <!--使用2-->
    withUnsafePointer(to: &age){print($0)}
    
    <!--使用3-->
    //其中p1的类型是 UnsafePointer<Int>
    let p1 = withUnsafePointer(to: &age) { ptr in
        return ptr
    }
    
    image
    由于withUnsafePointer方法中的闭包属于单一表达式,因此可以省略参数、返回值,直接使用$0,$0等价于ptr

    访问属性

    可以通过指针的pointee属性访问变量值,如下所示

    var age = 10
    let p = withUnsafePointer(to: &age) { $0 }
    print(p.pointee)
    
    <!--打印结果-->
    10
    

    如何改变age变量值?

    改变变量值的方式有两种,一种是间接修改,一种是直接修改

    • 间接修改:需要在闭包中直接通过ptr.pointee修改并返回。类似于char *p = “CJL” 中的 *p,因为访问CJL通过 *p
    var age = 10
    age = withUnsafePointer(to: &age) { ptr in
        //返回Int整型值
        return ptr.pointee + 12
    }
    print(age)
    
    • 直接修改-方式1:也可以通过withUnsafeMutablePointer方法,即创建方式一
    var age = 10
    withUnsafeMutablePointer(to: &age) { ptr in
        ptr.pointee += 12
    }
    
    • 直接修改方式2:通过allocate创建UnsafeMutablePointer,需要注意的是
      • initializedeinitialize是成对的

      • deinitialize中的count与申请时的capacity需要一致

      • 需要deallocate

    var age = 10
    //分配容量大小,为8字节
    let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
    //初始化
    ptr.initialize(to: age)
    ptr.deinitialize(count: 1)
    
    ptr.pointee += 12
    print(ptr.pointee)
    
    //释放
    ptr.deallocate()
    

    指针实例应用

    实战1:访问结构体实例对象

    定义一个结构体

    struct CJLTeacher {
        var age = 10
        var height = 1.85
    }
    var t = CJLTeacher()
    
    • 使用UnsafeMutablePointer创建指针,并通过指针访问CJLTeacher实例对象,有以下三种方式:
      • 方式一:下标访问

      • 方式二:内存平移

      • 方式三:successor

    //分配两个CJLTeacher大小的空间
    let ptr = UnsafeMutablePointer<CJLTeacher>.allocate(capacity: 2)
    //初始化第一个空间
    ptr.initialize(to: CJLTeacher())
    //移动,初始化第2个空间
    ptr.successor().initialize(to: CJLTeacher(age: 20, height: 1.75))
    
    //访问方式一
    print(ptr[0])
    print(ptr[1])
    
    //访问方式二
    print(ptr.pointee)
    print((ptr+1).pointee)
    
    //访问方式三
    print(ptr.pointee)
    //successor 往前移动
    print(ptr.successor().pointee)
    
    //必须和分配是一致的
    ptr.deinitialize(count: 2)
    //释放
    ptr.deallocate()
    

    需要注意的是,第二个空间的初始化不能通过advanced(by: MemoryLayout<CJLTeacher>.stride)去访问,否则取出结果是有问题

    image
    • 可以通过ptr + 1或者successor() 或者advanced(by: 1)
    <!--第2个初始化 方式一-->
    (ptr + 1).initialize(to: CJLTeacher(age: 20, height: 1.75))
    
    <!--第2个初始化 方式二-->
    ptr.successor().initialize(to: CJLTeacher(age: 20, height: 1.75))
    
    <!--第2个初始化 方式三-->
    ptr.advanced(by: 1).initialize(to:  CJLTeacher(age: 20, height: 1.75))
    

    对比

    • 这里p使用advanced(by: i * 8),是因为此时并不知道 p 的具体类型,必须指定每次移动的步长
    let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
    
    //存储
    for i in 0..<4 {
        //指定当前移动的步数,即i * 8
        p.advanced(by: i * 8).storeBytes(of: i + 1, as: Int.self)
    }
    
    • 这里的ptr如果使用advanced(by: MemoryLayout<CJLTeacher>.stride)即16*16字节大小,此时获取的结果是有问题的,由于这里知道具体的类型,所以只需要标识指针前进 几步即可,即advanced(by: 1)
    let ptr = UnsafeMutablePointer<CJLTeacher>.allocate(capacity: 2)
    //初始化第一个空间
    ptr.initialize(to: CJLTeacher())
    //移动,初始化第2个空间
    ptr.advanced(by: 1).initialize(to:  CJLTeacher(age: 20, height: 1.75))
    

    实战2:实例对象绑定到struct内存

    定义如下代码

    struct HeapObject {
        var kind: Int
        var strongRef: UInt32
        var unownedRef: UInt32
    }
    
    class CJLTeacher{
        var age = 18
    }
    
    var t = CJLTeacher()
    

    demo1:类的实例对象如何绑定到 结构体内存中?

    • 1、获取实例变量的内存地址
    • 2、绑定到结构体内存,返回值是UnsafeMutablePointer<T>
    • 3、访问成员变量 pointee.kind
    //将t绑定到结构体内存中
    //1、获取实例变量的内存地址,声明成了非托管对象
    /*
     通过Unmanaged指定内存管理,类似于OC与CF的交互方式(所有权的转换 __bridge)
     - passUnretained 不增加引用计数,即不需要获取所有权
     - passRetained 增加引用计数,即需要获取所有权
     - toOpaque 不透明的指针
     */
    
    let ptr = Unmanaged.passUnretained(t as AnyObject).toOpaque()
    //2、绑定到结构体内存,返回值是UnsafeMutablePointer<T>
    /*
     - bindMemory 更改当前 UnsafeMutableRawPointer 的指针类型,绑定到具体的类型值
        - 如果没有绑定,则绑定
        - 如果已经绑定,则重定向到 HeapObject类型上
     */
    let heapObject = ptr.bindMemory(to: HeapObject.self, capacity: 1)
    //3、访问成员变量
    print(heapObject.pointee.kind)
    print(heapObject.pointee.strongRef)
    print(heapObject.pointee.unownedRef)
    

    其运行结果如下,有点类似于CF与OC交互的时的所有权的转换


    image
    • create\copy 需要使用retain

    • 不需要获取所有权 使用unretain

    • 将kind的类型改成UnsafeRawPointer,kind的输出就是地址了

      image

    demo2:绑定到类结构

    swift中的类结构定义成一个结构体

    struct cjl_swift_class {
        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
    }
    
    • 将t改成绑定到cjl_swift_class
    //1、绑定到cjl_swift_class
    let metaPtr = heapObject.pointee.kind.bindMemory(to: cjl_swift_class.self, capacity: 1)
    //2、访问
    print(metaPtr.pointee)
    

    运行结果如下,其本质原因是因为 metaPtrcjl_swift_class的类结构是一样的

    image

    实战3:元组指针类型转换

    • 如果将元组传给 函数testPointer,使用方式如下
    var tul = (10, 20)
    
    //UnsafePointer<T>
    func testPointer(_ p : UnsafePointer<Int>){
        print(p)
    }
    
    withUnsafePointer(to: &tul) { (tulPtr: UnsafePointer<(Int, Int)>) in
        //不能使用bindMemory,因为已经绑定到具体的内存中了
        //使用assumingMemoryBound,假定内存绑定,目的是告诉编译器ptr已经绑定过Int类型了,不需要再检查memory绑定
        testPointer(UnsafeRawPointer(tulPtr).assumingMemoryBound(to: Int.self))
    }
    
    • 或者告诉编译器转换成具体的类型
    func testPointer(_ p: UnsafeRawPointer){
        p.assumingMemoryBound(to: Int.self)
    }
    

    实战4:如何获取结构体的属性的指针

    • 1、定义实例变量
    • 2、获取实例变量的地址,并将strongRef的属性值传递给函数

    代码如下:

    struct HeapObject {
        var strongRef: UInt32 = 10
        var unownedRef: UInt32 = 20
    }
    
    func testPointer(_ p: UnsafePointer<Int>){
       print(p)
    }
    //实例化
    var  t = HeapObject()
    //获取结构体属性的指针传入函数
    withUnsafePointer(to: &t) { (ptr: UnsafePointer<HeapObject>) in
        //获取变量
        let strongRef = UnsafeRawPointer(ptr) + MemoryLayout<HeapObject>.offset(of: \HeapObject.strongRef)!
        //传递strongRef属性的值
        testPointer(strongRef.assumingMemoryBound(to: Int.self))
    }
    

    实战5:通过 withMemoryRebound 临时绑定内存类型

    • 如果方法的类型与传入参数的类型不一致,会报错


      image.png

    解决办法:通过withMemoryRebound临时绑定内存类型

    var age = 10
    func testPointer(_ p: UnsafePointer<Int64>){
       print(p)
    }
    let ptr = withUnsafePointer(to: &age) {$0}
    ptr.withMemoryRebound(to: Int64.self, capacity: 1) { (ptr: UnsafePointer<Int64>)  in
        testPointer(ptr)
    }
    

    总结

    • 指针类型分两种

      • typed pointer 指定数据类型指针,即 UnsafePointer<T> + unsafeMutablePointer

      • raw pointer 未指定数据类型的指针(原生指针) ,即UnsafeRawPointer + unsafeMutableRawPointer

    • withMemoryRebound: 临时更改内存绑定类型

    • bindMemory(to: Capacity:): 更改内存绑定的类型,如果之前没有绑定,那么就是首次绑定,如果绑定过了,会被重新绑定为该类型

    • assumingMemoryBound假定内存绑定,这里就是告诉编译器:我的类型就是这个,你不要检查我了,其实际类型还是原来的类型

    相关文章

      网友评论

        本文标题:Swift-进阶 04:指针

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