私有API、Objective-C运行时与Swift | 作者:Victor Pavlychko
有时在开发应用程序时,我们可能会遇到需要使用私有 API 的情况。这些 API 可能提供了一些尚未公开的重要功能,或者可以用来解决已知的平台问题。也有可能,我们只是为了调试代码,想要获取更多的细节信息。
Swift 为这种场景增加了一个新的维度:标记为 NS_Swift_UNAVAILABLE 的受限 API。从技术上讲,这些 API 并非真正的私有,但由于安全性不足,它们不会在 Swift 中直接暴露。然而,调用这些不可用的 API 是可行的,因为它们通常有完整的文档,并且可以通过 Objective-C 代码访问。本文将重点介绍如何访问一组 Swift 不可用的 API,这些 API 涉及 NSMethodSignature、NSInvocation 以及与动态消息调度相关的类。如果你对这些内容不熟悉,建议先查阅相关文档。
Objective-C 运行时与私有 API
在涉及到 Objective-C 运行时时,几乎没有什么是完全私有的。尽管直接方法的引入稍微改变了这一点,但只要你知道如何调用这些 API,它们仍然是可访问的。
在 Objective-C 时代,调用未公开的 SDK 方法相对简单。由于 Objective-C 的动态特性,可以通过为私有方法声明一个类别来实现。这些方法会在运行时自动解析。然而,Swift 是一种类型安全的语言,这种方式在 Swift 中已经不可行。
在处理复杂签名时,常见的做法是依赖 Swift 兼容的 Foundation API,例如简单函数或低级的 method_getImplementation 和/或 objc_msgSend。但这种方式通常会导致代码复杂、冗长且容易出错。
更清洁的解决方案
我们可以借鉴 Objective-C 中的分类技巧,定义一个包含目标方法的 @objc 协议,通过运行时为目标类动态添加一致性,然后利用 Swift 的类型转换功能。由于 Objective-C 的动态调度特性,该协议会自动实现我们需要的现有类方法。
定义协议
首先,为 NSMethodSignature 定义一个协议,该协议需要与 Objective-C 文档中的内容相匹配。这里需要特别注意 @objc(getArgumentTypeAtIndex:) 注解。通常,编译器会根据方法名称生成适当的选择器,但在某些情况下,我们可能需要手动更改生成的名称。正确的选择器名称至关重要,因为它必须与底层 API 签名完全匹配。
@objc protocol NSMethodSignaturePrivate {
@objc(getArgumentTypeAtIndex:)
func getArgumentType(at index: UInt) -> UnsafePointer
}
动态附加协议
定义协议后,可以通过显式的 Objective-C 运行时调用将其附加到目标类:
let methodSignature = NSClassFromString("NSMethodSignature") as! NSObject.Type
let selector = NSSelectorFromString("signatureWithObjCTypes:")
let method = class_getClassMethod(methodSignature, selector)
虽然这段代码还缺少错误检查,但它是一个很好的辅助函数候选。测试一切正常后,可以进一步完善。
减少样板代码
在实现类导入代码时,可以尝试通过泛型减少协议引用的重复。例如:
func createSignature(for type: T.Type) -> T? {
// 示例代码
}
然而,这种方法可能会遇到编译错误,例如:
静态成员 signature(objCTypes:) 不能在协议元类型 NSMethodSignaturePrivate.Protocol 上使用。
这是因为 NSMethodSignaturePrivate 是一个协议,而不是具体的符合类型。为了调用类函数 signature(objCTypes:),需要一个具体的类型。
使用 Swift 反射 API
我们可以通过 Swift 反射 API 获取协议名称,然后利用 objc_getProtocol 找到对应的运行时协议:
let protocolName = "NSMethodSignaturePrivate"
if let protocolObj = objc_getProtocol(protocolName) {
// 使用 protocolObj
}
总结
Objective-C 运行时结合 Swift 的强大表现力,为开发者提供了许多可能性。通过一些技巧,我们可以访问 Swift 限制的或私有的 API,就像在 Objective-C 中那样。不过,需要注意的是,这种方式可能会绕过一些安全机制,因此需要谨慎使用。
完整的示例代码可以参考以下链接:
https://gist.github.com/victor-pavlychko/8a896917d8c73f4dded594ab4782214e
原文链接: https://medium.com/@victor.pavlychko/private-apis-objective-c-runtime-and-swift-ceaeefbb6e48