InterposeKit
InterposeKit 是一个使用 Swift 编写的现代库,用于优雅地 swizzle。它是完全使用 Swift 5.2+ 编写的,并且可以在 @objc dynamic
Swift 函数或 Objective-C 实例方法上工作。API 文档在interposekit.com 可用,并且在我的博客上的一些实现想法。
不同于基于method_exchangeImplementations
(见method_exchangeImplementations)添加新方法和交换实现,这个库直接使用class_replaceMethod
(见class_replaceMethod)来替换实现。这避免了swizzling的常见问题。
您可以在方法调用之前、之后或替代原有实现添加代码。
这与Aspects库类似,但目前还不支持动态子类。
使用方法
假设你要修改TestClass
中的sayHi
class TestClass: NSObject {
@objc dynamic func sayHi() -> String {
print("Calling sayHi")
return "Hi there 👋"
}
}
try Interpose(TestClass.self) {
try $0.hook(#selector(TestClass.sayHi), { store in { `self` in
print("Before Interposing \(`self`)")
let string = store((@convention(c) (AnyObject, Selector) -> String).self)(`self`, store.selector)
print("After Interposing \(`self`)")
return string + testSwizzleAddition
}
as @convention(block) (AnyObject) -> String})
}
调用print(TestClass().sayHi())
时,我们会得到以下结果。
[Interposer] Swizzled -[TestClass.sayHi] IMP: 0x000000010d9f4430 -> 0x000000010db36020
Before Interposing <InterposeTests.TestClass: 0x7fa0b160c1e0>
Calling sayHi
After Interposing <InterposeTests.TestClass: 0x7fa0b160c1e0>
Hi there 👋 and Interpose
关键事实
- 直接修改
方法
的实现,比基于选择器的 swizzling 更好。 - 纯 Swift,没有使用
NSInvocation
,这需要装箱并且可能会比较慢。 - 没有类型检查。如果你有拼写错误或忘记
约定
部分,这将在运行时崩溃。 - 是的,你需要两次输入结果类型。这是一个权衡,否则我们需要使用 NSInvocation 或汇编。
- 延迟插桩有助于在运行时加载类的场景。这对于 Mac Catalyst 很有用。
延迟挂钩
有时可能需要挂钩系统框架中的一个位于深处的类,该类是在晚些时候加载的。Interpose有针对此问题的解决方案,并在动态链接器中使用一个挂钩来通知何时加载新类。
try Interpose.whenAvailable(["RTIInput", "SystemSession"]) {
let lock = DispatchQueue(label: "com.steipete.document-state-hack")
try $0.hook("documentState", { store in { `self` in
lock.sync {
store((@convention(c) (AnyObject, Selector) -> AnyObject).self)(`self`, store.selector)
}} as @convention(block) (AnyObject) -> AnyObject})
try $0.hook("setDocumentState:", { store in { `self`, newValue in
lock.sync {
store((@convention(c) (AnyObject, Selector, AnyObject) -> Void).self)(`self`, store.selector, newValue)
}} as @convention(block) (AnyObject, AnyObject) -> Void})
}
常见问题
为什么不命名为跨接呢?“套件”感觉太老套了。
原计划将其命名为跨接,但随后出现了SR-898问题。虽然模块名称相同的类在大多数情况下是可以工作的,但当启用分发构建时,这会导致问题。有关修复此问题的讨论已有一些了,但这将在2020年末实现,如果真的能实现的话。
我想钩入 Swift!你又一如既往地做了 ObjC swizzle 操作,为什么?
UIKit 和 AppKit 还没有消失,问题也还没有消失。我认为这是一个不常需要的工具来解决系统级问题。Swift 中有一些方法可以解决这些问题,但那是一个独立(且更困难!)的项目。
我可以发这个吗?
当然可以。这个项目的目标是一个不力求显示过聪明才智的简单库。我在Aspects中这样做过,虽然我非常喜欢它,但它存在一些问题,可能会与其他试图显示聪明才智的代码产生副作用。InterposeKit很普通,所以你不必担心像“我们给我们的应用程序添加了New Relic,现在你的东西崩溃了”这样的问题。
甚至安全漏洞现在都有标识了。
已经2020年了,就连安全漏洞现在都有标识了。与时俱进吧!我知道,我明白。很快就会提升的!
它不执行 X
欢迎提交拉取请求!在提交之前最好先打开一个草稿来规划你的计划,我希望保持功能集最小化,使其保持简单无魔法。
安装
构建 InterposeKit 需要 Xcode 11.4 或更高版本或 Swift 5.2 或更高版本的工具链,并带有 Swift Package Manager。
Swift包管理器
将以下代码.package(url: "https://github.com/steipete/InterposeKit.git", from: "0.0.1")
添加到您的Package.swift
文件中的dependencies
部分。
Cocoapods
将 pod 'InterposeKit'
添加到您的 Podfile
中。
Carthage
将 github "steipete/InterposeKit"
添加到您的 Cartfile
文件中。
改进建议
- 编写提案,允许将现有类型的调用约定转换为其他约定。
- 使用C块结构在方法类型和C类型之间进行类型检查(我在Aspects库中这样做),这仍然会导致运行时崩溃,但可能是在钩子时间而不是调用它时发生。
- 添加基于对象的钩子,使用动态子类化(再次使用Aspects)。
- 为纯C函数添加dyld_dynamic_interpose钩子。
- 将Promise-API用于
Interpose.whenAvailable
,以实现更好的错误冒泡。 - 尝试使用Swift钩子?
⚡️ - 针对Swift Nightly进行测试,作为Chron Jpb。
- 我相信还有更多——欢迎拉取请求或评论!
感谢
特别感谢JP Simard,他在使用GitHub Actions搭建Yams方面做出了巨大的贡献——这极大地方便了在这里快速构建CI。
许可证
InterposeKit遵循MIT许可证。