未知指向类型时,能否将UnsafeMutableRawPointer转换为正确类类型?
这个问题问到点子上了!你的直觉其实没错——直接从无类型的UnsafeMutableRawPointer里猜出它指向的是A还是B,确实没有天生的安全办法,但咱们可以通过一些Swift的类型特性或者小技巧来解决这个问题,下面给你拆解几个可行的方案:
方案一:给A、B添加共同父类(最稳妥的常规方案)
如果可以修改A和B的定义,给它们加一个共同的父类(推荐继承NSObject,因为和C API交互起来更顺畅),是最安全的做法。这样我们可以先把指针转成父类实例,再用Swift的类型判断来区分A和B:
// 新增共同父类 class Base: NSObject {} class A: Base {} class B: Base {} protocol P {} extension A: P {} extension B: P {} // 传递实例给C API时,先转成Base类型的指针 let aInstance = A() SomeCFunction(/* 其他参数 */, Unmanaged.passUnretained(aInstance).toOpaque()) // 在回调中处理指针 { (pointerToEitherAOrB: UnsafeMutableRawPointer) in // 先转成父类Base的实例 let baseInstance = Unmanaged<Base>.fromOpaque(pointerToEitherAOrB).takeUnretainedValue() // 再判断具体类型 if let a = baseInstance as? A { // 这里拿到了A类型的实例,可以处理相关逻辑 print("处理A实例") } else if let b = baseInstance as? B { // 这里拿到了B类型的实例 print("处理B实例") } }
这里要注意Unmanaged的使用:如果C API会长期持有这个指针,要用passRetained;如果只是临时使用,用passUnretained即可,避免内存泄漏或者野指针问题。
方案二:给协议添加类型标识,配合C API传递额外信息
如果不能给A、B加父类,那可以给协议P加一个静态的类型标识属性,然后让C API额外接收这个标识(如果C API支持修改参数的话)。这样回调时就能根据标识直接判断类型:
protocol P { // 给每个遵循类加唯一的类型标识 static var typeID: String { get } } class A {} class B {} extension A: P { static var typeID: String { "ClassA" } } extension B: P { static var typeID: String { "ClassB" } } // 假设C API可以接收额外的类型标识参数 let bInstance = B() SomeCFunctionWithTypeArg( /* 其他参数 */, Unmanaged.passUnretained(bInstance).toOpaque(), B.typeID.cString(using: .utf8)! ) // 回调时同时拿到指针和类型标识 { (pointer: UnsafeMutableRawPointer, typePtr: UnsafePointer<CChar>) in let typeStr = String(cString: typePtr) switch typeStr { case A.typeID: let a = Unmanaged<A>.fromOpaque(pointer).takeUnretainedValue() print("处理A实例") case B.typeID: let b = Unmanaged<B>.fromOpaque(pointer).takeUnretainedValue() print("处理B实例") default: print("未知类型") } }
这个方案的局限性在于需要C API支持额外参数,如果C API是固定的不能修改,那这个方法就用不了。
方案三:用@objc协议辅助类型判断(仅限类类型)
如果你的协议P可以标记为@objc(因为只有类类型能遵循@objc协议,刚好你的A、B都是类),那可以先把指针转成AnyObject,再尝试向下转型:
@objc protocol P {} class A {} class B {} extension A: P {} extension B: P {} // 传递指针给C API let aInstance = A() SomeCFunction(/* 其他参数 */, Unmanaged.passUnretained(aInstance).toOpaque()) // 回调处理 { (pointer: UnsafeMutableRawPointer) in let object = Unmanaged<AnyObject>.fromOpaque(pointer).takeUnretainedValue() if let a = object as? A { print("处理A实例") } else if let b = object as? B { print("处理B实例") } else if let pObject = object as? P { // 如果还有其他遵循P的类,可以在这里统一处理 print("处理遵循P的其他实例") } }
不过要注意,这个方法要求你的类是@objc兼容的——如果A、B是纯Swift类(没继承NSObject),需要给类加上@objcMembers标记,或者手动标记为@objc。
总结
你的直觉是对的:没有额外的类型信息或共同父类的话,直接从UnsafeMutableRawPointer转成A或B是不安全的,因为Swift无法识别无类型指针指向的具体类型。最推荐的是方案一(加共同父类),它兼容性最好,也最安全;如果不能改类结构,再根据C API的情况选择方案二或三。
内容的提问来源于stack exchange,提问作者Dennis Vennink

