Swift之深入解析“指针”的使用和实现
Posted Forever_wj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift之深入解析“指针”的使用和实现相关的知识,希望对你有一定的参考价值。
一、指针
① 指针类型
- Swift 中的指针分为两类:
- typed pointer 指定数据类型指针,即 UnsafePointer,其中 T 表示泛型;
- raw pointer 未指定数据类型的指针(原生指针) ,即 UnsafeRawPointer。
- Swift 与 OC 指针对比如下:
Swift | OC | 说明 |
---|---|---|
unsafePointer | 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,即为并没有指定存储时的内存大小,执行结果如下:
index:0 value:4
index:1 value:0
index:2 value:140735373901836
index:3 value:140735325050456
- 修改程序:通过 advanced(by:) 指定存储时的步长:
// 存储
for i in 0..<4 {
// 指定当前移动的步数,即i * 8
p.advanced(by: i * 8).storeBytes(of: i + 1, as: Int.self)
}
- 修改后的运行结果如下:
index:0 value:1
index:1 value:2
index:2 value:3
index:3 value:4
③ 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
}
- 由于 withUnsafePointer 方法中的闭包属于单一表达式,因此可以省略参数、返回值,直接使用 $0,$0 等价于 ptr,如下:
- 访问属性:可以通过指针的 pointee 属性访问变量值,如下所示:
var age = 10
let p = withUnsafePointer(to: &age) { $0 }
print(p.pointee)
<!--打印结果-->
10
- 如何改变 age 变量值?
-
- 改变变量值的方式有两种,一种是间接修改,一种是直接修改;
-
- 间接修改:需要在闭包中直接通过 ptr.pointee 修改并返回。类似于char *p = “YDW” 中的 *p,因为访问 YDW 通过 *p;
var age = 10
age = withUnsafePointer(to: &age) { ptr in
// 返回Int整型值
return ptr.pointee + 12
}
print(age)
-
- 直接修改可以通过 withUnsafeMutablePointer 方法,即创建方式一:
var age = 10
withUnsafeMutablePointer(to: &age) { ptr in
ptr.pointee += 12
}
-
- 直接修改也通过 allocate 创建 UnsafeMutablePointer,但需要注意:
-
-
- initialize 与 deinitialize 是成对的;
-
-
-
- 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()
二、指针实例应用
① 访问结构体实例对象
- 定义一个结构体:
struct YDWTeacher {
var age = 18
var height = 1.85
}
var t = YDWTeacher()
- 使用 UnsafeMutablePointer 创建指针,并通过指针访问 YDWTeacher 实例对象,有以下三种方式:下标访问、内存平移和 successor。
// 分配两个YDWTeacher大小的空间
let ptr = UnsafeMutablePointer<YDWTeacher>.allocate(capacity: 2)
// 初始化第一个空间
ptr.initialize(to: YDWTeacher())
// 移动,初始化第2个空间
ptr.successor().initialize(to: YDWTeacher(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.stride) 去访问,否则取出结果是有问题,如下:
YDWTeacher(age:10, height:1.85)
YDWTeacher(age:14073292092791, height:5.4e-322)
YDWTeacher(age:10, height:1.85)
YDWTeacher(age:14073292092791, height:5.4e-322)
- 可以通过 ptr + 1 、 successor() 或者 advanced(by: 1);
<!--第2个初始化 方式一-->
(ptr + 1).initialize(to: YDWTeacher(age: 20, height: 1.75))
<!--第2个初始化 方式二-->
ptr.successor().initialize(to: YDWTeacher(age: 20, height: 1.75))
<!--第2个初始化 方式三-->
ptr.advanced(by: 1).initialize(to: YDWTeacher(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.stride) 即16*16字节大小,此时获取的结果是有问题的,由于这里知道具体的类型,所以只需要标识指针前进几步即可,即 advanced(by: 1):
let ptr = UnsafeMutablePointer<YDWTeacher>.allocate(capacity: 2)
// 初始化第一个空间
ptr.initialize(to: YDWTeacher())
// 移动,初始化第2个空间
ptr.advanced(by: 1).initialize(to: YDWTeacher(age: 20, height: 1.75))
② 实例对象绑定到 struct 内存
- 定义如下结构体:
struct HeapObject {
var kind: Int
var strongRef: UInt32
var unownedRef: UInt32
}
class YDWTeacher {
var age = 18
}
var t = YDWTeacher()
- 问题一:类的实例对象如何绑定到结构体内存中?
-
- 获取实例变量的内存地址;
-
- 绑定到结构体内存,返回值是 UnsafeMutablePointer;
-
- 访问成员变量 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交互的时的所有权的转换:
HeapObject(kind:4205000648, strongRef:3, unownedRef:0)
-
- create/copy 需要使用 retain;不需要获取所有权使用 unretain;将 kind 的类型改成 UnsafeRawPointer,kind 的输出便为地址;
HeapObject(kind:0x0000000100008248, strongRef:3, unownedRef:0)
- 问题二:绑定到类结构
-
- 将 swift 中的类结构定义成一个结构体:
struct ydw_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 改成绑定到 ydw_swift_class:
// 1、绑定到cjl_swift_class
let metaPtr = heapObject.pointee.kind.bindMemory(to: ydw_swift_class.self, capacity: 1)
// 2、访问
print(metaPtr.pointee)
-
- 运行结果如下,其本质原因是因为 metaPtr 和 ydw_swift_class 的类结构是一样的:
HeapObject(kind:0x0000000100008248, strongRef:3, unownedRef:0)
ydw_swift_class(kind:0x0000000100008220, superClass:0x00007fff88a976f8, cachedata1:0x00007fff201fbaf0, cachedata2:0x000080200000000, data:0x00000001006185b2, flags:2, instanceAddressOffset:0, instanceize:24, flinstanceAlignMask:7, reserved:0, classSize:136, classAddressOffset:16, description:0x0000000100003c44)
③ 元组指针类型转换
- 如果将元组传给函数 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)
}
④ 如何获取结构体的属性的指针
- 定义实例变量;
- 获取实例变量的地址,并将 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))
}
⑤ 通过 withMemoryRebound 临时绑定内存类型
- 如果方法的类型与传入参数的类型不一致,会报错:
- 解决办法:通过 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 + unsafeMutablePointer;
- raw pointer 未指定数据类型的指针(原生指针) ,即UnsafeRawPointer + unsafeMutableRawPointer;
- withMemoryRebound: 临时更改内存绑定类型;
- bindMemory(to: Capacity:): 更改内存绑定的类型,如果之前没有绑定,那么就是首次绑定,如果绑定过了,会被重新绑定为该类型;
- assumingMemoryBound 假定内存绑定,会通知编译器。
以上是关于Swift之深入解析“指针”的使用和实现的主要内容,如果未能解决你的问题,请参考以下文章
Swift之深入解析Sendable和@Sendable闭包代码实例
Swift之深入解析Sendable和@Sendable闭包代码实例
Swift之深入解析闭包Closures的使用和捕获变量的原理