Swift进阶-指针

OC中我们对指针的分类和结构都进行了比较详细的研究,那Swift中的指针又是怎样的,又是怎样使用的?本文将对它的类型,创建以及使用进行一一介绍

Swift指针简介

  • Swift中的指针分为两类,指定数据类型指针(typed pointer)未指定数据类型指针(raw pointer)raw pointer也称为原指针
    • raw pointer指针在swift中是用UnsafeRawPointer来表示
    • typed pointerswift中是用UnsafePointer<T>来表示,它是泛型,T是要指定的类型。
    • 可以发现swift指针都带有Unsafe,因为swift指针是对内存直接操作,是不安全的。

与OC指针的对应关系

  • Swift指针与OC指针的对应关系如下:
Swift OC 说明
unsafePointer<T> const T * 指针及所指向的内存都不可变
unsafeMutablePointer T * 指针及所指向的内存均可改变
unsafeRawPointer const void * 指针指向未知类型,指向的内存不可变
unsafeMutableRawPointer void * 指针指向未知类型,指向的内存可变

Swift指针的用法

  • 在使用Swift指针时,内存管理需要手动管理,也就是创建的指针在最后需要调用deallocate

原始指针的使用

  • 原始指针案例如下:

    let pointer = UnsafeMutableRawPointer.allocate(byteCount: 8, alignment: 8) // 创建指针
    for i in 0..<4 { // 从pointer指针首地址处开始存,之后每平移8字节存储一个Int类型的数
        pointer.advanced(by: i * 8).storeBytes(of: i+1, as: Int.self)
    }
    for i in 0..<4 { // 从pointer指针首地址开始读,之后每平移8字节读一次
        let value = pointer.load(fromByteOffset: i * 8, as: Int.self)
        print("index : \(i),  value : \(value)")
    }
    pointer.deallocate() // 手动销毁
    复制代码
    • 使用步骤如下:
      • 调用allocate函数创建一个8字节,并遵循8字节对齐的指针pointer
      • pointer指针首地址处开始存储,之后每平移8字节存储一个数
        • advanced(by:)函数是存储位置,如果不设置,就会存在指针的首地址处
      • 通过内存平移读取内存中的内容
      • 调用deallocate函数对指针进行手动销毁

创建类型指针的方式

  • OC中直接打印&+指针即可获得指针地址,而Swift中则不是,下面介绍两种方式来得到类型指针

withUnsafePointer(to:

  • withUnsafePointer(to:方法创建指针如下:

    截屏2021-12-30 08.57.43.png

  • 下面来查看下withUnsafePointer(to:的源码:

    @inlinable public func withUnsafePointer<T, Result>(to value: inout T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result
    复制代码
    • 该方法传入的参数value带有inout标记,则传参时需要传入地址
    • 然后参数body是一个闭包,它参数的类型的指针,且有返回值
    • 最后就是throws将闭包body的返回值抛出,然后rethrows将返回值抛给withUnsafePointer函数
  • 获取到指针后,可以使用pointer.pointee来打印指针指向的值

    截屏2021-12-30 11.30.22.png

  • 由于withUnsafePointer中的返回值Result类型不是固定的,所以也可以这样写:

    截屏2021-12-30 11.52.16.png

    • 此时pointer不是指针,而是int类型的值,且num值发生了改变
  • throwsrethrows的使用:

    func getAge(_ num: Int, completed: (Int) -> String) -> String {
        let result = completed(num)
        return result
    }
    // 调用
    let age = getAge(20) { "\($0)" }
    复制代码
    • 当闭包中有返回值且整个方法有返回值时,我们通常需要拿到闭包的返回值,再进行retrun给方法本身
    • 而使用throwsrethrows的话,只需将闭包结果抛出即可:
    func getAge(_ num: Int, completed: (Int) throws -> String) rethrows -> String {
        try completed(num)
    }
    复制代码
  • throwsrethrows区别:

    • throws:表示这个函数可能会抛出异常,在调用时需要在do-catch中加上try,如下例子

      func getAge(_ num: Int) throws -> String {
          return "\(num)"
      }
      do {
          let age = try getAge(20)
      } catch {}
      复制代码
    • rethrows:表示这个函数本身不会抛出异常,但如果作为参数的闭包抛出了异常,那么它会把异常继续抛上去

allocate

  • 类型指针还可以使用allocate来创建:

    let num = 20
    
    let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
    ptr.initialize(to: num)
    ptr.deinitialize(count: 1)
    ptr.deallocate()
    复制代码
    • 创建代码的相关方法作用如下:
      • allocate(capacity:):根据类型创建内存,count是要分配对应类型的内存量
      • deallocate:销毁内存,与allocate成对出现
      • initialize(to:):进行初始化to是变量
      • deinitialize(count:)deinitialize去初始化count去初始化的实例数,不能为负数,initializedeinitialize成对出现

Swift指针的一些使用场景

结构体类型的指针使用

  • 创建一个WSPerson结构体,然后创建两个类型指针:

    struct WSPerson {
        var name: String = "ws"
        var age: Int = 18
    }
    
    let ptr = UnsafeMutablePointer<WSPerson>.allocate(capacity: 2)
    ptr.initialize(to: WSPerson())  // 在ptr内存首地址处初始化一个WSPerson实例
    (ptr+1).initialize(to: WSPerson(name: "dj", age: 20)),在ptr首地址平移一个WSPerson内存单位处存储第二个WSPerson实例
    ptr.deinitialize(count: 2) // 去初始化数量为2
    ptr.deallocate()
    复制代码
    • 代码中主要通过指针内存平移来初始化两个WSPerson实例
    • (ptr+1)是从ptr首地址开始向下平移WSPerson内存,也可以这样写
      ptr.advanced(by: 1) // 这里平移1是因为已经知道类型,所以以此处的1代表一个`WSPerson`内存单位,与(ptr+1)是一样的
      复制代码
    • 访问WSPerson的实例可以用以下几种方法:
        1. ptr.pointee, (ptr+1).pointee
        1. ptr[0], ptr[1]
        1. (ptr + 1).predecessor().pointee, ptr.successor().pointee

    predecessor是上一个连续的WSPerson大小的指针,successor是下一个连续的WSPerson大小的指针,与内存平移道理一样

    • 验证结果如下:

    截屏2021-12-30 15.19.27.png

对象绑定到结构体类型

对象绑定到对象的结构体类型

  • 有类Wushuang和结构体HeapObject,代码如下:

    struct HeapObject {
        var kind: UnsafeRawPointer
        var strongref: UInt32
        var unownedred: UInt32
    }
    
    class Wushuang {
        var age: Int = 18
    }
    复制代码
    • Swift进阶-类&对象&属性 分析得出的swift对象的本质是HeapObject,所以此处写一个结构类似的结构体用来绑定对象
  • 对象绑定结构体的具体代码如下:

    var ws = Wushuang()
    
    let p = Unmanaged.passUnretained(ws as AnyObject).toOpaque() // 获取实例对象
    let heapObj = p.bindMemory(to: HeapObject.self, capacity: 1) // 绑定到具体的类型
    复制代码
    • Unmanaged.passUnretained:获取实例对象ws的指针
      • Unmanaged:是托管,类似于OC中的__bridge进行所有权转换
      • passUnretained:不需要所有权,只需要指针,得到的是UnsafeMutableRawPointer指针
    • bindMemory:绑定到具体的类型,内存大小为一个HeapObject内存大小,此时得到结果就是HeapObject类型的指针
  • 使用相关打印验证如下:

    截屏2021-12-30 17.39.16.png

对象中isa绑定到类的结构体类型

  • 我们知道HeapObject中的kind实质就是isa,它指向了类,但上面的例子类的结构类型还没有绑定。在 Swift进阶-类&对象&属性 中我们知道swift类的结构,可以将它写成伪代码如下:

    struct swift_class_t {
        var isa: UnsafeRawPointer
        var superclass: UnsafeRawPointer
        var cacheData: UnsafeRawPointer
        var data: UnsafeRawPointer
        var flags: UInt32
        var instanceAddressOffset: UInt32
        var instanceSize: UInt32
        var instanceAlignMask: UInt16
        var reserved: UInt16
        var classSize: UInt32
        var classAddressOffset: UInt32
        var description: UnsafeRawPointer
    }  
    复制代码
  • heapObjkind绑定到swift_class_t类型得到metadata

    let metadata = heapObj.pointee.kind.bindMemory(to: swift_class_t.self, capacity: 1)
    复制代码
    • 打印metadata结果如下:

    截屏2021-12-30 18.04.10.png

  • 之所以能这样绑定,是因为本质上真是的swift_class结构,和此处伪代码的一样,所以不影响存取

元组类型指针的使用

  • 代码如下:

    var turple = (18, 22)
    withUnsafePointer(to: &turple) { ptr in
        print(ptr)
    }
    复制代码
    • 在查看ptr类型时发现是UnsafePointer<(Int, Int)>类型,而指针是根据内存平移来访问值,也就是指针的类型应该是UnsafePointer<Int>才能满足要求
  • 这种情况需要使用assumingMemoryBound(to:)函数,具体代码如下

    var turple = (18, 22)
    withUnsafePointer(to: &turple) { ptr in
        testPointer(UnsafeRawPointer(ptr).assumingMemoryBound(to: Int.self))
    }
    
    func testPointer(_ t: UnsafePointer<Int>) {
        print(t)
    }
    复制代码
    • 由于ptr是已初始化指针,所以先将ptr强转成UnsafeRawPointer,然后去绑定类型,但此时不能使用bindMemory,因为本质上ptr已经绑定了类型,所以此时要使用assumingMemoryBound 假装绑定,强转后的新指针,假装去绑定Int类型,进而将类型转成UnsafePointer<Int>
  • 打印结果如下: 截屏2021-12-30 19.59.40.png

获取结构体中变量的指针

  • 核心代码与元组类型一样,需要使用assumingMemoryBound进行假设绑定.
  • 有两种方式获取结构体变量的指针:获取首地址再平移直接获取结构体变量地址,将两种方法放在一块对比:
    struct SHeapObject {
        var strongref: Int = 10
        var unownedref: Int = 20
    } 
    
    func testPointer(_ t: UnsafePointer<Int>) {
        print(t)
    }
    
    var h = SHeapObject()
    withUnsafePointer(to: &h) { ptr in
        // 第一种 获取首地址,通过内存平移拿到unownedref地址
        testPointer(UnsafeRawPointer(ptr).assumingMemoryBound(to: Int.self)) 
        // 第二种,直接获取unownedref地址进行假装绑定
        let unownedred = UnsafeRawPointer(ptr) + MemoryLayout<HeapObject>.offset(of: \HeapObject.strongref)!
        testPointer(unownedred.assumingMemoryBound(to: Int.self))
    }
    复制代码
    • offset(of:)是变量相对的路径,需要带\,打印结果如下:
    截屏2021-12-30 20.37.11.png
    • 结果二者得到的地址一样

临时更改内存绑定类型

  • 当我们得到的Int类型指针时,使用时需要转成UInt64型的指针,就需要用到withMemoryRebound(to: , capacity:)方法来进行 临时更改绑定类型 ,具体代码如下:

    var age = 20
    let ptr = withUnsafePointer(to: &age) { $0 }
    
    let uInt64Ptr = ptr.withMemoryRebound(to: UInt64.self, capacity: 1) { $0 }
    testPointer(uInt64Ptr)
    
    func testPointer(_ t: UnsafePointer<UInt64>) {
        print(t)
    }
    复制代码

总结

    1. Swift中的指针分为两类,指定数据类型指针(typed pointer)  和 未指定数据类型指针(raw pointer)
    1. 创建指针 的方式有两种:
    • 调用withUnsafePointer(to:)方法创建
    • 通过allocate方法进行创建,但需要调用deallocate进行销毁
    1. 绑定类型
    • bindMemory:将指针绑定到具体类型,用于未知类型指针去绑定类型
    • assumingMemoryBound: 假装绑定新的类型,用于某些特殊类型,例如获取结构体某个变量的指针
    • withMemoryRebound:临时更改绑定类型,用于需要指针类型类型转成其他类型使用,例如Int类型指针需要转成UInt64使用

Supongo que te gusta

Origin juejin.im/post/7047502044143288350
Recomendado
Clasificación