开发文档架构层面的设计待读清单

Runtime窥探 (六)| AOP与Aspects核心源码

2017-10-20  本文已影响325人  Dely

前言

如何把这个世界变得美好?把你自己变得更美好

秋天来了

我们这篇博客继续来介绍Runtime在开发中的实际应用,通过开源库Aspects来对runtime有更好的认识和理解。

一、Aspects库

这个库是iOS基于AOP编程思想的开源库,用于跟踪修改一个指定的类的某个方法执行前/替换/后,同时可以自定义添加一段代码块.对这个类的所有对象都会起作用。

所有的调用都会是线程安全的.Aspects 使用了Runtime的消息转发机制以及method swizzling,会有一定的性能消耗.所有对于过于频繁的调用,不建议使用 Aspects.Aspects更适用于视图/控制器相关的等。并不适用于每秒调用不超过1000次的代码.

二、AOP

基本概念:

主要功能:

主要意图:

说明:

注意:

三、解析Aspects库

我们经常用的Method Swizzling就是一种AOP思想实现,Aspects是比较很棒的基于AOP编程思想的开源库,
由于Aspects的代码较多,我们只是来阅读Aspects的核心实现思路和流程。

1.Aspects的基本模块

上面这些模块都是用来辅助核心思想实现的,使开源库模块清晰、较高容错率、职责明确等等,这些模块还是比较好理解的,就不一一阅读了。其实很多优秀开源库都会有类似的模块(比如信息、容器、唯一标识等等)。下面我们主要了解Aspects的核心思想以及流程。

2.小插曲

为什么大多数开源库都会有这些模块?举个例子:

和女朋友一起去溜一堆狗......

3.Aspects对外接口以及基本说明

通过源代码Aspects中可以看到下面两个对外公开接口用于hook selector

@interface NSObject (Aspects)

/***********************
第一个参数selector:是要给它增加切面的原方法
第二个参数是AspectOptions:是代表这个切片增加在原方法的before / instead / after
第三个入参block:这个block复制了正在被hook的方法的签名signature类型
第一个参数selector将返回一个遵循<AspectInfo>的id对象,这个对象继承了方法的所有参数,
这些参数都会被填充到匹配的block的签名里
你也可以使用一个空block,或者一个简单的id<AspectInfo>
不支持hook静态static方法的
返回一个可以用来撤销aspect的token
***********************/

//hook类方法,hook一个类的所有实例对应的一个方法
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                           withOptions:(AspectOptions)options
                            usingBlock:(id)block
                                 error:(NSError **)error;
//hook实例方法,hook类的一个具体实例对应的一个方法
//为一个具体实例的seletor的执行 之前/或者被替换/之后 添加一个block代码
- (id<AspectToken>)aspect_hookSelector:(SEL)selector
                           withOptions:(AspectOptions)options
                            usingBlock:(id)block
                                 error:(NSError **)error;

@end

方法说明:

上面是Aspects的核心思想以及流程的简单说明,下面我们对这些核心代码进行梳理介绍。

注意:

4.Aspects核心代码解析

对外接口源码

1.上图说明:

下面我们对函数aspect_prepareClassAndHookSelector(self, selector, error);来进行查看源码

2.核心函数aspect_prepareClassAndHookSelector()

函数aspect_prepareClassAndHookSelector()的具体实现如下:

aspect_prepareClassAndHookSelector

3.hook Class过程

我们先来看一下核心函数aspect_prepareClassAndHookSelector()中的第一部分hook Class过程,这个会调用aspect_hookClass函数

aspect_hookClass

上面就是整个hook Class的过程,流程图如下:

hook Class

上面都会调用有一个函数aspect_swizzleClassInPlace,这个函数的作用就是我们来替换Class系统方法forwardInvocation:的实现,源代码如下

//替换类的快速消息转发方法,并把类添加到交换类的集合中
static Class aspect_swizzleClassInPlace(Class klass) {
    NSCParameterAssert(klass);
    NSString *className = NSStringFromClass(klass);

    _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
        if (![swizzledClasses containsObject:className]) {
            //不包含,就调用aspect_swizzleForwardInvocation()方法,并把className加入到Set集合里面。
            aspect_swizzleForwardInvocation(klass);
            [swizzledClasses addObject:className];
        }
    });
    return klass;
}

//类的forwardInvocation方法替换为__ASPECTS_ARE_BEING_CALLED__的实现,返回新函数imp
static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";
static void aspect_swizzleForwardInvocation(Class klass) {
    NSCParameterAssert(klass);

    //替换类中已有方法的实现,返回原来函数imp
    IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
    
    if (originalImplementation) {
        //originalImplementation不为空的话说明原方法有实现,添加一个新方法保存原来类的ForwardInvocation方法实现
        class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
    }
    AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}

hook Class总结:到这里就把hook Class的过程解析完成了,说到底过程就是处理要被hook的类,同时把类的消息转发方法forwardInvocation:替换成__ASPECTS_ARE_BEING_CALLED__函数。

4.hook selector过程

在核心函数aspect_prepareClassAndHookSelector()中的hook Class处理在上面已经解析完成,现在我们来继续往下解析hook selector。这一部分源码如下:

Method targetMethod = class_getInstanceMethod(klass, selector);
IMP targetMethodIMP = method_getImplementation(targetMethod);
if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
    //当前imp不是消息转发方法
    //获取当前原始的selector对应的IMP的方法编码typeEncoding
    const char *typeEncoding = method_getTypeEncoding(targetMethod);
    
    //给原始方法添加一个前缀名"aspects__XX"
    SEL aliasSelector = aspect_aliasForSelector(selector);
    
    if (![klass instancesRespondToSelector:aliasSelector]) {
        //没有找到新方法"aspects__XX",就添加一个新方法
        __unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
        NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
    }
    
    //我们使用消息转发forwardInvocation来进行hook
    //把当前的sel方法的替换成forwardInvocation方法,selector被执行的时候,直接会触发消息转发从而进入forwardInvocation
    class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
    AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
}

hook selector总结:说到底这个过程就是处理要被hook的selector,把selector方法的实现替换成的消息转发方法forwardInvocation:

5.举例说明核心流程:

我们准备hook初始化阶段调用下面代码:

[a aspect_hookSelector:@selector(method) withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo>aspectInfo,){
        NSLog(@"arguments = %@",aspectInfo.arguments);    
} error:NULL];

我们在hook执行过程调用下面代码:

[a method];

5.Aspects总结

Aspects核心思想就是通过runtime的消息转发机制和method swizzling生成中间类来替换函数实现。这种思想和上一篇KVO的底层实现很相似。可以仔细阅读里面的代码,学习相关的实现思想以及优秀的代码片段。

我没有把所有的代码都一一解析,如果想看代码注释的,博客最后会有注释的项目地址。我对Aspects的库的中文注释以及理解说明,有兴趣的可以下载看下。

有注释的Aspects地址:https://git.coding.net/Dely/JYAOPDemo.git

上一篇 下一篇

猜你喜欢

热点阅读