Swift之深入解析“指针”的使用和实现

Posted Forever_wj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift之深入解析“指针”的使用和实现相关的知识,希望对你有一定的参考价值。

一、指针

① 指针类型
  • Swift 中的指针分为两类:
    • typed pointer 指定数据类型指针,即 UnsafePointer,其中 T 表示泛型;
    • raw pointer 未指定数据类型的指针(原生指针) ,即 UnsafeRawPointer。
  • Swift 与 OC 指针对比如下:
SwiftOC说明
unsafePointerconst T *指针及所指向的内容都不可变
unsafeMutablePointerT *指针及其所指向的内存内容均可变
unsafeRawPointerconst void *指针指向未知类型
unsafeMutableRawPointervoid *指针指向未知类型
② 原生指针
  • 什么是原生指针?
    • 原生指针是指未指定数据类型的指针;
    • 对于指针的内存管理是需要手动管理的;
    • 指针在使用完需要手动释放。
  • 有以下一段原生指针的使用代码,请问运行时会发生什么?
	// 原生指针
	// 对于指针的内存管理是需要手动管理的
	// 定义一个未知类型的指针:本质是分配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之深入解析如何实现Promise

Swift之深入解析闭包Closures的使用和捕获变量的原理

Swift之深入解析如何使用Swift UI实现3D Scroll效果

Swift之深入解析协议Protocol的底层原理