阅读 791

Aspects swift 源代码分析

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, 参数处理更优雅

github repo

文章分类
iOS
文章标签