Unmanaged与withUnsafePointer API的差异及指针使用问题
原始测试代码
class Dummy { let uuid = UUID() } func test() { let dummy = Dummy() let unmangedOpaquePointer = Unmanaged.passUnretained(dummy).toOpaque() let fromWithUnsafeAPIPointer = withUnsafePointer(to: dummy, { UnsafeMutableRawPointer(mutating: $0) }) print(unmangedOpaquePointer == fromWithUnsafeAPIPointer) // false let dummy1 = Unmanaged<Dummy>.fromOpaque(unmangedOpaquePointer).takeUnretainedValue() let dummy2 = fromWithUnsafeAPIPointer.assumingMemoryBound(to: Dummy.self).pointee let dummy3 = Unmanaged<Dummy>.fromOpaque(fromWithUnsafeAPIPointer).takeUnretainedValue() let dummy4 = unmangedOpaquePointer.assumingMemoryBound(to: Dummy.self).pointee print(dummy1 === dummy2) // true print(dummy1 === dummy3) // EXC_BAD_ACCESS print(dummy2 === dummy4) // EXC_BAD_ACCESS } test()
问题1:验证指针指向同一对象的方法
执行以下代码输出为false:
print(unmangedOpaquePointer == fromWithUnsafeAPIPointer) // false
两个UnsafeMutableRawPointer实例本身不相等,但它们实际指向同一个dummy对象。既然无法通过指针相等性验证这一点,该如何操作?
问题2:API配对使用的原因及指针差异
代码中dummy1 === dummy2输出true,说明从两个指针获取的是同一实例,但dummy1 === dummy3和dummy2 === dummy4会触发EXC_BAD_ACCESS崩溃。看起来Unmanaged系列API返回的指针只能搭配UnmanagedAPI使用,withUnsafePointer系列API返回的指针同理,请问这是为什么?
修正withUnsafePointer用法后的测试代码
class Dummy { let uuid = UUID() } func test() { var dummy = Dummy() let unmangedOpaquePointer = Unmanaged.passUnretained(dummy).toOpaque() withUnsafeMutablePointer(to: &dummy) { let fromWithUnsafeAPIPointer = UnsafeMutableRawPointer($0) print(unmangedOpaquePointer == fromWithUnsafeAPIPointer) // false let dummy1 = Unmanaged<Dummy>.fromOpaque(unmangedOpaquePointer).takeUnretainedValue() let dummy2 = fromWithUnsafeAPIPointer.assumingMemoryBound(to: Dummy.self).pointee let dummy3 = Unmanaged<Dummy>.fromOpaque(fromWithUnsafeAPIPointer).takeUnretainedValue() let dummy4 = unmangedOpaquePointer.assumingMemoryBound(to: Dummy.self).pointee print(dummy1 === dummy2) // true print(dummy1 === dummy3) // EXC_BAD_ACCESS print(dummy2 === dummy4) // EXC_BAD_ACCESS } } test()
修正withUnsafePointer的错误用法后,执行结果与原始代码一致。
问题解答
针对问题1的解决方案
要验证两个指针是否指向同一对象,不能直接比较指针本身的相等性,而是要通过指针指向的对象实例来判断:
- 对于
Unmanaged获取的指针,用takeUnretainedValue()取出实例; - 对于
withUnsafePointer获取的指针,用assumingMemoryBound(to:).pointee取出实例; - 最后用
===比较两个实例是否为同一引用,就像代码中dummy1 === dummy2那样,结果为true就说明指针指向同一对象。
另外也可以通过比较对象的唯一标识(比如示例中的uuid)来辅助验证:
print(dummy1.uuid == dummy2.uuid) // true
针对问题2的原因解析
这两个API返回的指针本质上指向的内存位置完全不同:
Unmanaged.passUnretained(_:).toOpaque():返回的是Swift对象的实际内存地址(即对象的引用本身指向的地址)。Unmanaged的设计目的是处理与C/ObjC交互时的对象引用,它直接操作的是对象的底层引用,所以必须用Unmanaged.fromOpaque(_:).takeUnretainedValue()来还原——这个方法知道如何从对象的实际地址中正确取出实例。withUnsafePointer(to:):返回的是变量dummy本身的内存地址,而不是变量指向的对象的地址。变量dummy是一个存储了对象引用的栈变量,所以这个指针指向的是栈上存储引用的位置,而不是堆上的对象实例。当你用assumingMemoryBound(to: Dummy.self).pointee时,Swift会从这个栈地址中读取存储的引用,进而拿到对象实例;但如果用Unmanaged.fromOpaque(_:)处理这个指针,它会错误地把栈地址当成对象的实际地址去解析,自然会触发内存访问错误——因为栈地址根本不是Swift对象的有效内存区域。
简单来说:
Unmanaged的指针指向堆上的对象实例;withUnsafePointer的指针指向栈上的变量(存储对象引用的容器);
二者指向的内存区域完全不同,所以必须用对应的API来解析,跨API使用就会访问错误的内存地址,导致崩溃。
内容的提问来源于stack exchange,提问作者Jiang Wang

