Swift

Swift中的指针

2021-01-05  本文已影响0人  YY323

指针

Swift中指针分为两类:

Swift 中的指针和 OC 中指针的对应关系如下:

注意: 指针的内存管理需要手动管理,使用完后需要手动释放

例:使用Raw Pointer在内存中连续存储4个整型数据

// 以8字节对齐,分配32字节大小的内存空间
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)

// 存储值到内存
for i in 0..<4 {
    // 通过advanced(by:)指定存储时的步长
    p.advanced(by: i * 8).storeBytes(of: i + 1, as: Int.self)
}

// 从内存中读取值
for i in 0..<4 {
    // 通过内存平移来读取内存中的值
    let value = p.load(fromByteOffset:i * 8, as: Int.self)
    print("index\(i), value:\(value)")
}
// 指针的内存管理需要手动管理
p.deallocate()

首先了解Swift$0$1的含义:
Swift自动为闭包提供参数名缩写功能,可以直接通过$0$1 来表示闭包中的第一个、第二个参数,并且对应的参数类型会根据函数类型来进行判断。

在前面,我们获取一个变量本身的地址都是通过 withUnsafePointer(to:) 的方式获取。
其函数定义如下:

@inlinable public func withUnsafePointer<T, Result>(to value: inout T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result

可看到:

var age : Int = 10

// 第一种:单一表达式:相当于将ptr赋值给p,p的类型UnsafePointer<Int>
let p = withUnsafePointer(to: &age) { ptr in
    return ptr
}

// 第二种:此时p为print($0)的返回结果,类型为print方法的返回类型Void
let p = withUnsafePointer(to: &age) { print($0) }

// 第三种:$0赋值给p,p的类型UnsafePointer<Int>
let p = withUnsafePointer(to: &age) { $0 }

从上可知:withUnsafePointer 这个函数result 类型取决于其参数闭包表达式的返回值,第一种和第三种得到的 p 都是UnsafePointer<Int>类型,可以通过 p.pointee 获取 age 的值。

还可以使用 withUnsafePointer 这个函数修改 age 的值:

age = withUnsafePointer(to: &age, { ptr in
    return ptr.pointee + 12
})
withUnsafeMutablePointer(to: &age, { ptr in
    ptr.pointee = 122
})

执行 上面代码,age 的值就直接被修改为 122 了。

// 分配容量为1的内存空间(Int8字节)
let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 1)

// 初始化
ptr.initialize(to: 22)

// 手动管理内存:对应initialize
ptr.deinitialize(count: 1)

// 手动管理内存:对应allocate
ptr.deallocate()

这时 ptr.pointee 读出来则为22。

指针实例演示

实例1:访问结构体指针

struct YYTeacher {
    var age = 12
    var name = "YY"
}

var t = YYTeacher()

// 分配装2个struct的内存空间
let ptr = UnsafeMutablePointer<YYTeacher>.allocate(capacity: 2)

// 初始化第一个struct的内存空间
ptr.initialize(to: YYTeacher())

// 第一种方式:初始化第二个struct的内存空间
ptr.successor().initialize(to: YYTeacher(age: 22, name: "EE"))

// 第二种方式:初始化第二个struct的内存空间
//(ptr + 1).initialize(to: YYTeacher(age: 22, name: "EE"))

// 第三种方式:初始化第二个struct的内存空间
//ptr.advanced(by: 1).initialize(to:YYTeacher(age: 22, name: "EE"))

// 两种方式获取第一个struct的值
print(ptr[0])
print(ptr.pointee)

// 三种方式获取第二个struct的值
print(ptr[1])
print((ptr + 1).pointee)
print(ptr.successor().pointee)
print(ptr.advanced(by: 1).pointee)

// 手动管理内存:对应initialize
ptr.deinitialize(count: 2)

// 手动管理内存:对应allocate
ptr.deallocate()

通过查看源码可知:successor() 本质就是 advanced(by:1)

对比上面 Raw Pointer 例子中的p.advanced(by: i * 8) ,因为上面的 p未知类型,要存储Int类型的值就必须每次指定移动 8 的倍数,这里的8是一个Int类型所占的字节数;而在 UnsafeMutablePointer 中初始化第二个struct内存空间时使用 ptr.advanced(by:1) ,这里已知 ptr结构体指针,只需要告知往前移动几步就可以了,这里的 1 相当于往前移动 1个struct步长 (这里的 struct 步长为24)就好,两个 struct 相差的字节大小为1 * 24
实例2:将实例对象绑定到结构体内存中

struct HeapObject {
    var kind: Int
    var strongRef: UInt32
    var unownedRef: UInt32
}

class YYTeacher {
    var age = 20
}

var t = YYTeacher()

首先:获取实例对象的值
其次:将 raw pointer 绑定到具体类型指针

// 获取实例对象的值,返回值类型为 UnsafeMutableRawPointer
let ptr = Unmanaged.passUnretained(t as AnyObject).toOpaque()

// 将当前Raw Pointer-->ptr重新绑定到HeapObject结构体,返回值类型为 UnsafeMutablePointer<HeapObject>
let heapObject = ptr.bindMemory(to: HeapObject.self, capacity: 1)
// 访问内存中的值
print(heapObject.pointee)

扩展:如何将 heapObject 的属性 kind 绑定到 Swift 类结构的结构体内存中?(此时的 kindUnsafeRawPointer 类型)

struct 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 instanceAlignMask:UInt16
    var reserved:UInt16
    var classSize:UInt32
    var classAddressOffset:UInt32
    var description: UnsafeRawPointer
}
// 绑定内存
let metaptr = heapObject.pointee.kind.bindMemory(to: swift_class.self, capacity: 1)
// 访问内存中的值
print(metaptr.pointee)

运行结果如下图:

其本质是因为 metaptrswift_class 的内存结构是一样的。

实例3:元祖指针类型转换
如何将元祖 tul 的指针传给 testPointer 函数?

var tul = (10,20)

func testPointer(_ p: UnsafePointer<(Int)>) {
    print(p)
}

在上面例子中,testPointer 函数的参数是一个UnsafePointer<(Int)> 类型的,而 tul 的指针类型为 UnsafePointer<(Int, Int)> ,这时就需要将 tul 的指针类型重新绑定内存。如下:

withUnsafePointer(to: &tul) {(tulPtr : UnsafePointer<(Int, Int)>) in
    // 这里不能使用bindMemory,因为tulPtr已经绑定到具体类型了
    // assumingMemoryBound:假定内存绑定,告诉编译器tulPtr已经绑定过Int类型了,不需要再检查memory绑定
    testPointer(UnsafeRawPointer(tulPtr).assumingMemoryBound(to: Int.self))
}

那么:bindMemory(to:capacity:)assumingMemoryBound(to:)以及withMemoryRebound(to:capacity:_:)用法有什么区别呢?

var age = 12

func testPointer(_ p: UnsafePointer<UInt64>) {
   print(p)
}

let ptr = withUnsafePointer(to: &age){$0}
ptr.withMemoryRebound(to: UInt64.self, capacity: 1) {
   testPointer($0)
}

在这里 ptr 本身是 Int 类型的指针,使用 withMemoryRebound临时将其指针类型绑定到 UInt64 类型的内存,使用结束后,其类型重新绑定成 Int 类型的指针。

上一篇 下一篇

猜你喜欢

热点阅读