前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS开发 面向切面编程之 Aspects 源码解析

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

作者头像
iOSSir
发布2023-03-19 11:51:11
6410
发布2023-03-19 11:51:11
举报

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

2、基本原理

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

从上面我们可以发现,在发消息的时候,如果 selector 有对应的 IMP,则直接执行,如果没有就进行查找,如果最后没有查找到。OC 给我们提供了几个可供补救的机会,依次有:

resolveInstanceMethod

forwardingTargetForSelector

forwardInvocation

Aspects 之所以选择在 forwardInvocation 这里处理是因为,这几个阶段特性都不太一样:

resolvedInstanceMethod

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

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

Aspects hook的过程

在没有hook之前,ViewController的SEL与IMP关系如下

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

然后,我们再来看看hook后,一个viewWillAppear:的实际调用顺序:

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

1、判断能否hook 对Class和MetaClass进行进行合法性检查,判断能否hook,规则如下

规则如下:

1).retain,release,autorelease,forwoardInvocation:不能被hook

2).dealloc只能在方法前hook 3).类的继承关系中,同一个方法只能被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一个对象的实例方法的时候:

hook实例方法详解

hook的过程:

1、通过statedClass = self.class获取self本来的class (class方法被重写了,用来获取self被hook之前的Class(Target))

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

3、如果baseClass(实际指向的class)已经是被hook过的子类,则返回baseClass。

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

5.如果不是上述3、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)

class被hook后的情况:

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-01-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Python课后小剧场 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档