steipete/Aspects
的设计,挺高深莫测的
- 一般我们 hook, hook 一个类的所有实例的方法
这里 hook 的粒度,到了对象级别
- 而且不需要建立,大量的 IMP
利用到了固有的方法转发的流程
本文主要参考 woshiccm/Aspect
分析下 Aspects 的 swift 源代码实现
Aspects 的大概设计
1, 方法换一个时机执行
1.1, 方法转发的流程
方法执行的时候,将 hook 的 selector 的实现 IMP 替换为 _objc_msgForward 或者_objc_msgForward_stret,
此时调用 selector, 就会进行消息转发
1.2,实际做事情的函数
将 forwardInvocation 的实现, 替换为自定义的函数
__ASPECTS_ARE_BEING_CALLED__
并添加 __aspects_forwardInvocation 的实现为 forwardInvocation 原来的实现。
我们 hook 的方法 selector ,需要进行消息转发
我们 hook 的方法 selector, 都会执行自定义的函数
__ASPECTS_ARE_BEING_CALLED__
1.3, 自定义的函数中
执行了原来的方法,和 hook 的 block
这样,原来的方法,还是跑了,但是在一个不同的时机
方便我们处理
2, 需要执行的 hook 方法
得到了有利的时机
把 hook 在前面,在后面,用来替换的方法,给执行了
2.1, 执行对应的 block
Aspects 中,把 hook 的 block 的描述信息中,捞出 NSMethodSignature
使用 NSMethodSignature,执行 hook 的 block
2.2,执行原来的方法
NSInvocation 的实例, invoke 下
Swift Aspect 中的具体实现
3.1 调用
有这么个实例方法
@objc dynamic func test(id: Int, name: String) {
print("come on")
}
hook 上面那个方法
_ = try? hook(selector: #selector(ViewController.test(id:name:)), strategy: .before) { (_, id: Int, name: String) in
print("done")
}
开始 hook
@discardableResult
func hook<Arg1, Arg2>(
selector: Selector,
strategy: AspectStrategy = .before,
block: @escaping(AspectInfo, Arg1, Arg2) -> Void) throws -> AspectToken
{
// 拿到需要插入执行的 block
let wrappedBlock: @convention(block) (AspectInfo) -> Void = { aspectInfo in
guard aspectInfo.arguments.count == 2,
let arg1 = aspectInfo.arguments[0] as? Arg1,
let arg2 = aspectInfo.arguments[1] as? Arg2 else { return }
block(aspectInfo, arg1, arg2)
}
let wrappedObject: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self)
// 配合 hook 住的方法,去 hook
return try hook(selector: selector, strategy: strategy, block: wrappedObject)
}
添加分类,
语法糖,便利方法
extension NSObject {
public func hook(selector: Selector, strategy: AspectStrategy = .before, block: AnyObject) throws -> AspectToken {
return try ahook(object: self, selector: selector, strategy: strategy, block: block)
}
}
3.2 hook 主要流程
真正做事情的
func ahook(object: AnyObject, selector: Selector, strategy: AspectStrategy, block: AnyObject) throws -> AspectToken {
// 加锁保护
return try lock.performLocked {
// 记录所有的信息
let identifier = try AspectIdentifier.identifier(with: selector, object: object, strategy: strategy, block: block)
// 存起来
let cache = getAspectCache(for: object, selector: selector)
cache.add(identifier, option: strategy)
// 为了 hook
// 开始准备
// 创建待 hook 的子类
let subclass: AnyClass = try hookClass(object: object, selector: selector)
let method = class_getInstanceMethod(subclass, selector) ?? class_getClassMethod(subclass, selector)
guard let impl = method.map(method_getImplementation), let typeEncoding = method.flatMap(method_getTypeEncoding) else {
throw AspectError.unrecognizedSelector
}
assert(checkTypeEncoding(typeEncoding))
let aliasSelector = selector.alias
if !subclass.instancesRespond(to: aliasSelector) {
// 这里是,把方法原本的实现,添加到另一个 selector
// 目的是,调用被 hook 的方法
let succeeds = class_addMethod(subclass, aliasSelector, impl, typeEncoding)
precondition(succeeds, "not OK")
}
// 将被 hook 方法的实现改为
// forwardInvocation ( 消息转发 )
// 与上面的 1.1, 一致
class_replaceMethod(subclass, selector, _aspect_objc_msgForward, typeEncoding)
return identifier
}
}
3.3, 动态创建子类 ( 有点 KVO 那味 )
private func hookClass(object: AnyObject, selector: Selector) throws -> AnyClass {
let perceivedClass: AnyClass = object.objcClass
// 获取 object 的 isa 指针
let realClass: AnyClass = object_getClass(object)!
let className = String(cString: class_getName(realClass))
if className.hasPrefix(Constants.subclassPrefix) {
// 之前 hook 过
// 当 hook 一个对象的 selector 时会生成一个子类,
// 子类给了一个前缀
// 如 object 对应的类就是生成的子类,直接返回
return realClass
} else if class_isMetaClass(realClass) {
// 判断是否为类对象,如果是,则直接在当前类中进行 swizzle
if class_getInstanceMethod(perceivedClass, selector) == nil {
// 判断是否为 KVO 过的对象,
// 因为 KVO 的对象 ISA 指针指向一个中间类,
// 则直接在这个间接类,进行 swizzle
// 中间类就是,我们动态创建的子类
// hook 了,继续 hook
swizzleForwardInvocation(realClass)
return realClass
} else {
swizzleForwardInvocation(perceivedClass)
return perceivedClass
}
}
let subclassName = Constants.subclassPrefix+className
let subclass: AnyClass? = subclassName.withCString { cString in
if let existingClass = objc_getClass(cString) as! AnyClass? {
return existingClass
} else {
// 动态创建子类
if let subclass: AnyClass = objc_allocateClassPair(perceivedClass, cString, 0) {
// 替换当前类的 forwardInvocation 的方法实现为
// __ASPECTS_ARE_BEING_CALLED__
// 这里的 aspectForwardInvocation
swizzleForwardInvocation(subclass)
// 把生成子类的 isa 指针, 指向原来的类
// 并且,把生成子类的元类的 isa 指针,指向原来的类
replaceGetClass(in: subclass, decoy: perceivedClass)
// 注册当前生成的子类
objc_registerClassPair(subclass)
return subclass
} else {
return nil
}
}
}
guard let nonnullSubclass = subclass else {
throw AspectError.failedToAllocateClassPair
}
// 将当前对象的 isa 指针
// 指向刚生成的子类
// 即 KVO 的中间类
object_setClass(object, nonnullSubclass)
return nonnullSubclass
}
3.4 方法交换
交换实现,把方法转发的实现,使用自定义的匿名函数 closure, aspectForwardInvocation
aspectForwardInvocation, 相当于 OC 版本的
__ASPECTS_ARE_BEING_CALLED__
private func swizzleForwardInvocation(_ realClass: AnyClass) {
guard let originalImplementation = class_replaceMethod(realClass,
ObjCSelector.forwardInvocation,
imp_implementationWithBlock(aspectForwardInvocation as Any),
ObjCMethodEncoding.forwardInvocation) else {
return
}
}
把原来的方法,和插入的 block 都给执行了
private let aspectForwardInvocation: @convention(block) (Unmanaged<NSObject>, AnyObject) -> Void = { objectRef, invocation in
// 准备执行的信息
let object = objectRef.takeUnretainedValue() as AnyObject
let selector = invocation.selector!
var aliasSelector = selector.alias
var aliasSelectorKey = AssociationKey<AspectsCache?>(aliasSelector)
let selectorKey = AssociationKey<AspectsCache?>(selector)
let associations = Associations(object.objcClass as AnyObject)
let aspectCache: AspectsCache
// 掏出缓存
if let cache = associations.value(forKey: selectorKey) {
aspectCache = cache
} else if let cache = (objectRef.takeUnretainedValue() as NSObject).associations.value(forKey: aliasSelectorKey) {
aspectCache = cache
} else {
return
}
var info = AspectInfo(instance: object, invocation: invocation)
// Before hooks.
// 插入前
aspectCache.beforeAspects.invoke(with: &info)
if aspectCache.insteadAspects.isEmpty {
// 执行原来的
invocation.setSelector(aliasSelector)
invocation.invoke()
} else {
// Instead hooks
// 执行,插入替换
aspectCache.insteadAspects.invoke(with: &info)
}
// 插入后
// After hooks.
aspectCache.afterAspects.invoke(with: &info)
}
3.5 之前的准备,信息采集
把 hook 住的 selector 的信息,和插入的执行 block , 都记录下来
static func identifier(with selector: Selector, object: AnyObject, strategy: AspectStrategy, block: AnyObject) throws -> AspectIdentifier {
guard let blockSignature = AspectBlock(block).blockSignature else {
throw AspectError.missingBlockSignature
}
do {
try isCompatibleBlockSignature(blockSignature: blockSignature, object: object, selector: selector)
var aspectIdentifier = AspectIdentifier(selector: selector, object: object, strategy: strategy, block: block)
aspectIdentifier.blockSignature = blockSignature
return aspectIdentifier
} catch {
throw error
}
}
hook 前的判断
如果 hook 的函数签名,与插入的 block 的函数签名,对不上,
就搞不下去了
签名信息参数前两位是默认的,
Argument 0 是 self/block,
argument 1 是 SEL or id<AspectInfo>,
所以从 index = 2 开始校验, 也就是第 3 位
static func isCompatibleBlockSignature(blockSignature: AnyObject, object: AnyObject, selector: Selector) throws {
let perceivedClass: AnyClass = object.objcClass
let realClass: AnyClass = object_getClass(object)!
let method = class_getInstanceMethod(perceivedClass, selector) ?? class_getClassMethod(realClass, selector)
guard let nonnullMethod = method, let typeEncoding = method_getTypeEncoding(nonnullMethod) else {
object.doesNotRecognizeSelector?(selector)
throw AspectError.unrecognizedSelector
}
let signature = NSMethodSignature.signature(objCTypes: typeEncoding)
var signaturesMatch = true
if blockSignature.objcNumberOfArguments > signature.objcNumberOfArguments {
// block 签名参数一定是,不大于方法签名参数
// 检查签名的长度
signaturesMatch = false
} else {
if blockSignature.objcNumberOfArguments > 1 {
// 判断第 2 位
let rawEncoding = blockSignature.getArgumentType(at: UInt(1))
let encoding = ObjCTypeEncoding(rawValue: rawEncoding.pointee) ?? .undefined
// // 遵循 AspectInfo 协议对象
if encoding != .object {
signaturesMatch = false
}
}
if signaturesMatch {
// 从第 3 位,开始判断
for index in 2 ..< blockSignature.objcNumberOfArguments {
let methodRawEncoding = signature.getArgumentType(at: index)
let blockRawEncoding = blockSignature.getArgumentType(at: index)
let methodEncoding = ObjCTypeEncoding(rawValue: methodRawEncoding.pointee) ?? .undefined
let blockEncoding = ObjCTypeEncoding(rawValue: blockRawEncoding.pointee) ?? .undefined
if methodEncoding != blockEncoding {
signaturesMatch = false
break
}
}
}
}
if !signaturesMatch {
throw AspectError.blockSignatureNotMatch
}
}
特色:
Aspects 的源代码,有很多强转的地方
因为 runtime 的好多数据结构,不对外公开
所以需要自个,定义一些类似的结构
编译时期,runtime 内部的内存结构
与我们定义的内存结构,
是一一对应得上的,那就可以强转
对比
Swift 版本的逻辑,基本转化自 OC 版本的
-
__aspect_forwardInvocation:, 没用的方法,同样没用 -
alias 方法,原来的方法名加前缀
就像交换变量一样, 把 a 的值,换为 b 的值
原方法的 IMP, 变方法转发
添加 alias 方法,跑原方法的 IMP
Swift 版本的,够用,存在不足
缺点:
Swift 版本的,比起 OC 版本的,
功能不全 ( 一般用不到的 )
现存的功能,也没 OC 版本的强大
OC 版本的 hook 的 block, 参数处理更优雅