从AOP框架学习iOS Runtime

2016-03-02 22:09165人阅读评论(0)收藏举报

本文章已收录于:

iOS知识库

分类:

iOS(276)

目录(?)[+]

最近在蚂蚁聚宝iOS项目中添加了一个AOP框架,并且根据项目需求做了一些重构,重构的过程中对AOP的实现方式也做了下学习和分析,感觉还是很有趣的,下面就给大家分享一下个人所得。

在正式讲解iOS中AOP实现原理之前,有些准备知识需要讲一下

准备知识

准备知识一:Method,SEL,IMP概念

SEL

先看一下SEL的概念,Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL。

SEL也是@selector的类型,用来表示OC运行时的方法的名字。来看一下OC中的定义

本质上,SEL只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的KEY值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度。这个查找过程我们将在下面说明。

我们可以在运行时添加新的selector,也可以在运行时获取已存在的selector。

IMP

实际上是一个函数指针,指向方法实现的首地址,定义如下:

关于IMP的几点说明:

使用当前CPU架构实现的标准的C调用约定

第一个参数是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针)

第二个参数是方法选择器(selector),

从第三个参数开始是方法的实际参数列表。

通过取得IMP,我们可以跳过Runtime的消息传递机制,直接执行IMP指向的函数实现,这样省去了Runtime消息传递过程中所做的一系列查找操作,会比直接向对象发送消息高效一些,当然必须说明的是,这种方式只适用于极特殊的优化场景,如效率敏感的场景下大量循环的调用某方法。

Method

直接上定义:

Method = SEL + IMP + method_types,相当于在SEL和IMP之间建立了一个映射

准备知识二:iOS方法调用流程

方法调用的核心是objc_msgSend方法:

objc_msgSend(receiver, selector, arg1,arg2,…)

具体的过程如下:

先找到selector 对应的方法实现(IMP),因为同一个方法可能在不同的类中有不同的实现,所以需要receiver的类来找到确切的IMP

IMP class_getMethodImplementation(Class class, SEL selector)

如同其文档所说:The function pointer returned may be a function internal to the runtime instead of an actual method implementation. For example, if instances of the class do not respond to the selector, the function pointer returned will be part of the runtime's message forwarding machinery.

具体来说,当找不到IMP的时候,方法返回一个 _objc_msgForward 对象,用来标记需要转入消息转发流程,我们现在用的AOP框架也是利用了这个机制来人为的制造找不到IMP的假象来触发消息转发的流程

如果实在对_objc_msgFroward的内部实现感兴趣,只能看看源码了,只不过都是汇编实现的....感兴趣的同学可以想想为什么是用汇编来实现

这里有个源码的镜像https://github.com/opensource-apple,如果翻墙费劲的话

根据查找结果

找到了IMP,调用找到的IMP,传入参数

没找到IMP,转入消息转发流程

将IMP的返回值作为自己的返回值

补充说明一下IMP的查找过程,消息传递的关键在于objc_class结构体中的以下几个东西:

Class *isa

Class *super_class

objc_method_list **methodLists

objc_cache *cache

当消息发送给一个对象时,objc_msgSend通过对象的isa获取到类的结构体,然后在cache和methodLists中查找,如果没找到就找其父类,以此类推知道找到NSObject类,如果还没找到,就走消息转发流程。

准备知识三:iOS方法转发流程

从上文中我们看到当obj无法查找到 IMP时,会返回一个特定的IMP _objc_msgForward , 然后会进入消息转发流程,具体流程如下:

动态方法解析

resolveInstanceMethod:解析实例方法

resolveClassMethod:解析类方法

通过class_addMethod的方式将缺少的selector动态创建出来,前提是有提前实现好的IMP(method_types一致)

这种方案更多的是位@dynamic属性准备的

备用接受者(AOP中有使用)

如果上一步没有处理,runtime会调用以下方法

-(id)forwardingTargetForSelector:(SEL)aSelector

如果该方法返回非nil的对象,则使用该对象作为新的消息接收者

不能返回self,会出现无限循环

如果不知道该返回什么,应该使用[super forwardingTargetForSelector:aSelector]

这种方法属于单纯的转发,无法对消息的参数和返回值进行处理

完整转发(AOP中有使用)

- (void)forwardInvocation:(NSInvocation *)anInvocation

对象需要创建一个NSInvocation对象,把消息调用的全部细节封装进去,包括selector, target, arguments 等参数,还能够对返回结果进行处理

为了使用完整转发,需要重写以下方法

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector,如果2中return nil,执行methodSignatureForSelector:

因为消息转发机制为了创建NSInvocation需要使用这个方法吗获取信息,重写它为了提供合适的方法签名

AOP核心逻辑解析

到了有意思的戏肉部分,打算用流程图的方式解析一下核心的两个流程:拦截器(intercepter)注册流程和拦截器(intercepter)执行流程。

拦截器(intercepter)注册流程

说明:(图中m:代表Method,ClassA是AOP的目标类,X是AOP的目标方法,AOPAspect是AOP处理类-单例)

将原始的X的IMP拿出来,以特定的命名规则动态加入AOPAspect

将X的IMP替换为_objc_msgForward,用这种比较tricky的方式来触发消息转发流程

将ClassA中原有的forwardingTargetForSelector:的IMP以特定的命名规则存入AOPAspect

将ClassA的forwardingTargetForSelector:的IMP用AOPApect中的baseClassForwardingTargetForSelector替换,其中的具体逻辑见下面的代码

后边的就是将拦截器的信息和block存入到AOPAspect中,细节就不讲了,有兴趣的同学可以到github上看看原始版

拦截器(intercepter)执行流程

说明:(图中m:代表Method,ClassA是AOP的目标类,X是AOP的目标方法,AOPAspect是AOP处理类-单例,IMP是方法对应的实现)

开始调用,objc_msgSend开始查找SEL为X的IMP,查到结果为_objc_msgForward,触发ClassA的转发流程

ClassA中转发流程调用forwardingTargetForSelector:,实际会调用替换上去的baseClassForwardingTargetForSelector:的IMP,这个IMP正常情况下会返回AOPAspect的单例作为target(代码见上文图)

接下来开始在AOPAspect的单例中执行转发流程,经过一系列的3.1-3.5的跳转查找,最终会触发转发流程的forwardingInvocation方法

在forwardingInvocation中触发一系列的interceptors的执行(包括原始的X的IMP),代码见下图

后边的interceptor的执行细节也略过了,有兴趣的同学可以到github上看看原始版

总结

讲到这里iOS AOP框架中的核心的思路和流程就算是讲完了,其实这些之外其他的代码有兴趣的同学也可以看看,也欢迎大家拍砖~

转自:https://yq.aliyun.com/articles/3063

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,170评论 0 7
  • 前言 runtime其实在我们日常开发过程中很少使用到,尤其是像我现在比较初级的程序猿就更用不到了。但是去面试很多...
    WolfTin阅读 611评论 0 2
  • 继上Runtime梳理(四) 通过前面的学习,我们了解到Objective-C的动态特性:Objective-C不...
    小名一峰阅读 741评论 0 3
  • 1492年,哥伦布带着当时的旧地图,从西班牙出发向西航行,他推断东亚距离西班牙不到一万公里,当瞭望手贝尔梅霍高呼“...
    先为力胜阅读 253评论 0 1