iOS开发 面向切面编程之 Aspects 源码解析

一、面向切面编程应用在统计上 业务逻辑和统计逻辑常常耦合在一块儿,一方面影响了正常的业务逻辑,同时也很容易搞乱打点逻辑,并且要查看打点状况的时候也很分散。在 web 编程时候,这种场景很早就有了很成熟的方案,也就是所谓的AOP 编程(面向切面编程),其原理也就是在不更改正常的业务处理流程的前提下,经过生成一个动态代理类,从而实现对目标对象嵌入附加的操做。在 iOS 中,要想实现类似的效果也很简单,利用 oc 的动态性,经过 swizzling method 改变目标函数的 selector 所指向的实现,而后在新的实现中实现附加的操做,完成以后再回到原来的处理逻辑。 开源框架Aspects是一个很是好的框架。 Aspects git

二、基本原理github

原理1.png

每个对象都有一个指向其所属类的isa指针,经过该指针找到所属的类,而后会在所属类中的方法列表中寻找方法的实现,若是在方法列表中查到了和选择子名称相符的方法就会跳转到他的方法实现,若是找不到会向其父类的方法列表中查找,以此类推,直到NSObject类,若是仍是查找不到就会执行“消息转发”操做。 另外为了保证消息机制的效率,每个类都设置一个缓存方法列表,缓存列表中包含了当前类的方法以及继承自父类的方法,在查询方法列的时候,都会先查询本类的缓存列表,再去查询方法类别。这样当一个方法已经被调用过一次,下次调用就会很快的查询到并调用。web

方法调用的过程
1.在对象本身缓存的方法列表中去找要调用的方法,找到了就直接执行其实现。
2.缓存里没找到,就去上面说的它的方法列表里找,找到了就执行其实现。
3.还没找到,说明这个类本身没有了,就会经过isa去向其父类里执行一、2。
4.若是找到了根类还没找到,那么就是没有了,会转向一个拦截调用的方法,咱们能够本身在拦截调用方法里面作一些处理。
5.若是没有在拦截调用里作处理,那么就会报错崩溃。
复制代码

从上面咱们能够发现,在发消息的时候,若是 selector 有对应的 IMP,则直接执行,若是没有就进行查找,若是最后没有查找到。OC 给咱们提供了几个可供补救的机会,依次有 resolveInstanceMethod、forwardingTargetForSelector、forwardInvocation。编程

Aspects 之因此选择在 forwardInvocation 这里处理是由于,这几个阶段特性都不太同样:缓存

resolvedInstanceMethod 适合给类/对象动态添加一个相应的实现forwardingTargetForSelector 适合将消息转发给其余对象处理 forwardInvocation 是里面最灵活,最能符合需求的bash

所以 Aspects 的方案就是,对于待 hook 的 selector,将其指向 objc_msgForward,同时生成一个新的 aliasSelector 指向原来的 IMP,而且 hook 住 forwardInvocation 函数,使他指向本身的实现。按照上面的思路,当被 hook 的 selector 被执行的时候,首先根据 selector 找到了 objc_msgForward ,而这个会触发消息转发,从而进入 forwardInvocation。同时因为 forwardInvocation 的指向也被修改了,所以会转入新的 forwardInvocation 函数,在里面执行须要嵌入的附加代码,完成以后,再转回原来的 IMP。框架

Aspects hook的过程ide

static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
   NSCParameterAssert(self);
   NSCParameterAssert(selector);
   NSCParameterAssert(block);

   __block AspectIdentifier *identifier = nil;
   aspect_performLocked(^{

//首先判断
       if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) 
     {
           AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
           identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];

           if (identifier) 
         {
               [aspectContainer addAspect:identifier withOptions:options];

               // Modify the class to allow message interception.
               aspect_prepareClassAndHookSelector(self, selector, error);

           }//if (identifier) 

       }//if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) 

   });
   return identifier;
}

复制代码

在没有hook以前,ViewController的SEL与IMP关系以下 函数

hook以前.png

hook以后.png

最初的viewWillAppear: 指向了_objc_msgForward
增长了aspects_viewWillAppear:,指向最初的viewWillAppear:的IMP
最初的forwardInvocation:指向了Aspect提供的一个C方法__ASPECTS_ARE_BEING_CALLED__
动态增长了__aspects_forwardInvocation:,
指向最初的forwardInvocation:的IMP
复制代码

而后,咱们再来看看hook后,一个viewWillAppear:的实际调用顺序:ui

2.object收到selector(viewWillAppear:)的消息
2.找到对应的IMP:_objc_msgForward,执行后触发消息转发机制。
3.object收到forwardInvocation:消息
4.找到对应的IMP:__ASPECTS_ARE_BEING_CALLED__,执行IMP 

复制代码
//__ASPECTS_ARE_BEING_CALLED__中的逻辑
1.向object对象发送aspects_viewWillAppear:执行最初的viewWillAppear方法的IMP
2.执行插入的block代码
3.若是ViewController没法响应aspects_viewWillAppear,则向object对象发送__aspects_forwardInvocation:来执行最初的forwardInvocation IMP
复制代码

一、判断可否hook 对Class和MetaClass进行进行合法性检查,判断可否hook,规则以下 1).retain,release,autorelease,forwoardInvocation:不能被hook 2).dealloc只能在方法前hook 3).类的继承关系中,同一个方法只能被hook一次

2.建立AspectsContainer对象, 以"aspects_ "+ SEL为key,做为关联对象依附到被hook 的对象上

3.建立AspectIdentifier对象,而且添加到AspectsContainer对象里存储起来。这个过程分为两步 生成block的方法签名NSMethodSignature 对比block的方法签名和待hook的方法签名是否兼容(参数个数,按照顺序的类型) 4.根据hook实例对象/类对象/类元对象的方法作不一样处理。

A)类方法来hook的时候,分为两步

1.hook类对象的forwoardInvocation:方法,指向一个静态的C方法,
2.而且建立一个aspects_ forwoardInvocation:动态添加到以前的类中

3.hook类对象的viewWillAppear:方法让其指向_objc_msgForward,
4.动态添加aspects_viewWillAppear:指向最初的viewWillAppear:实现
复制代码

B)Hook实例的方法

Aspects支持只hook一个对象的实例方法

只不过在第4步略有出入,当hook一个对象的实例方法的时候:

1.新建一个子类,_Aspects_ViewController,而且按照上述的方式hook forwoardInvocation:

2.hook _Aspects_ViewController的class方法,让其返回ViewController
hook _Aspects_ViewController_MetaClass,让其返回ViewController

3.调用objc_setClass来修改ViewController的类为_Aspects_ViewController

这样作,就能够经过object_getClass(self)得到类名,而后看看是否有前缀类名来判断是否被hook过了
复制代码

hook实例方法详解

TestClass *testObj = [[TestClass alloc] init];

    [testObj aspect_hookSelector:NSSelectorFromString(@"testSelector")
                     withOptions:AspectPositionBefore
                      usingBlock:^(id<AspectInfo> aspectInfo) {
             
                            NSLog(@"Hook testSelector");
                                                                       }
                           error:NULL];
    [testObj testSelector];
复制代码

hook以前实例的状态.png

hook的过程: 一、经过statedClass = self.class获取self原本的class (class方法被重写了,用来获取self被hook以前的Class(Target))

二、经过Class baseClass = object_getClass(self)获取self的isa指针实际指向的class (self在运行时实际的class,表面上看这是一个西瓜(statedClass),实际上这是一个苹果(basedClass))

三、若是baseClass(实际指向的class)已是被hook过的子类,则返回baseClass。

4.若是baseClass是MetaClass或者被KVO过的Class,则没必要再生成subClass,直接在其自身上进行method swizzling。

5.若是不是上述三、4 所述状况,默认状况下须要对被hook的Class进行”isa swizzling”:

1)经过subclass = objc_allocateClassPair(baseClass, subclassName, 0)动态建立一个被hook类(TestClass)的子类(TestClass_Aspects); 2)而后对子类(TestClass_Aspects)的forwardInvocation:进行method swizzling,替换为_ASPECTS_ARE_BEING_CALLED_,进行消息转发时,实际执行的是_ASPECTS_ARE_BEING_CALLED_中的方法; 3)重写子类(TestClass_Aspects)的获取类名的方法class,使其返回被hook以前的类的类名(TestClass); 4)将self(TestObj)的isa指针指向子类(TestClass_Aspects)

object_setClass(self, subclass)
//object_setClass将一个对象设置为别的类类型,返回原来的Class
复制代码

class被hook后的状况:

hook后.png
相关文章
相关标签/搜索