这个是很重要的东西,我们不仅要知其然还要知其所以然。
由于OC是一门地地道道的动态语言,所以他的函数调用跟其他语言还是有很大区别的。
“大部分”语言的函数调用
其实都一个样子,无论什么语言最终都会转换成汇编语言,所以方法调用都是一个样子,就是直接或者间接的跳转到某个地址,这个地址就是函数的实现地址,并且是该函数实现的首地址。
其实OC的方法调用最终也是上面这种形式,只是他在进入最终的这个调用形式之前,四处瞎逛了一圈,逛完了最终才会进入上面的这个步骤,而他四处乱逛时所干的事情就是OC的消息发送机制了。
了解消息发送机制之前
说起消息发送,就不得不提一下类对象和元类对象了。
消息发送机制
消息发送机制可以分为三个过程:
1、消息发送过程(也就是方法查找过程)
2、动态方法解析过程
3、消息转发过程
方法分为方法声明和方法实现两个部分
正常情况下 可以用中括号调用的方法都是声明过的,但不一定是实现过的,没实现过的,编译没问题,但运行调用就可能会crash掉,但别担心,消息转发就是来拯救这个方法的,后边会说到。
1、消息发送过程(方法查找阶段)
我们都知道OC的方法可以分为加号方法和减号方法,他们的流程相同,但是找的地方不同,比如说减号方法,首先通过对象的isa指针找到其类对象,在类对象的cache找要调用的方法,如果找到了就直接跳转到该函数实现的首地址,如果没找到就在方法列表里查找,如果找到了,就直接调用该方法,并缓存到cache,如果还是没找到,就通过类对象的superclass找到父类的类对象,在父类的类对象cache找...如果一直找到superclass = nil都没找到 那么恭喜你 要进入下一个阶段了
2、动态方法解析阶段
在进入这个阶段之前,会首先判断是否已经动态解析过,如果动态解析过,那么这个阶段是会直接跳过的,直接进入消息转发阶段
进入方法解析,会调用一个方法,减号方法会调用- resolveInstanceMethond:
,加号方法会调用+ resolveClassMethond:
前边我们说过方法能被调用,说明至少被声明过,但消息发送既然能走到这里,说明该方法只有声明没有实现,而上边的加号和减号方法就是给你调用的这个方法加实现的,也就是具体的方法实现代码。
添加完实现之后会将这个动态方法解析阶段标记为已经执行过的状态,然后会重新进入第一阶段,为什么会重新进入第一阶段呢?是因为这个时候你已经为这个方法添加了实现,也就是把方法的实现添加进方法列表了,也就是说可以在方法列表里找到了。而如果你没有添加该方法的实现,那么在第一阶段的方法列表里说明还是找不到的,就会再次进入动态方法解析阶段,因为前边第一次动态解析已经标记过了,所以这个时候不会再进动态方法解析了,直接跳转到消息发送的最后阶段 消息转发!
3、消息转发阶段
说实话,消息发送能走到这个阶段的场景真的不是很多。
进入消息转发阶段后,会调用这个方法forwardingTargetForSelector:
这个方法是干嘛的,根据方法名翻译过来是 【转发选择器目标】这个方法会返回一个实例对象,如果返回的是nil,说明没有可以转发的目标,会进入下一个小步骤;如果返回了一个实例对象,则会在这个实例对象的类对象的cache和方法列表中查找这个方法,方法名还是跟之前一样没变,找到了就直接调用,没找到就crash了。
我们说一下当forwardingTargetForSelector :
返回nil的情况,进入下一个步骤,下一个步骤会调用methodSignatureForSelector :
这个方法调用完会返回一个方法签名methodSignature
当然也有可能返回nil,如果返回不为空,则进入forwardInvocation
这个方法进行重定向
附加知识NSProxy
其实有时候,可能会存在这样一种需求,那就是,不执行消息发送的第一阶段和第二阶段,能够直接一上来就直接进入消息转发阶段。这个其实苹果早已为我们想到了,那就是你自定义一个类继承自NSProxy,这个类和NSObject一样,是属于OC少有的基类,通过这个类,很特殊,特殊在哪儿呢,你会发现,生成这个类的一个对象时,只有一个alloc方法,并没有init方法,是不是感觉刷新了你对OC语言的认知新高度呢?当然它还有一个特殊点,就在于,一旦你实现了第三阶段的三个方法的时候,调用方法时,就会略过第一阶段和第二阶段,直接进入第三阶段。这样的话,就给消息转发提供了很大的一个捷径。
大家可能会纳闷,苹果怎么要专门搞这个一个类呢,而且主要作用还是这样的。这个原因的话,就得说到多继承了。大家都知道,现在开发中流行的很多开发语言都是单继承语言,就是因为多继承容易出现一系列麻烦的问题,一个是菱形继承的问题,一个是继承逻辑的问题,注意,多继承下是很容易出现这两种情况的,尤其是继承逻辑问题。这也是为啥如今很多面向对象语言都是单继承的原因。然鹅呢,多继承也是有它的优势之处的,例如本来某各类就同时拥有另外两个类的特性,那样的话,同时继承自这两个类是一种很好的方式。
因此,大部分单继承语言中,为了能保留多继承的优势,就另辟蹊径的实现了多继承,OC中当然也有这个另辟蹊径的方法,那就是这里提到的消息转发。大家可以想想,为啥第三阶段的名字非得叫做消息转发呢,听名字就能让你一下子明白这个阶段的主要作用了,那就是将本身类没有实现的方法转发到另一个类的方法实现。举个例子,类A有test1和test2的方法实现,类B有test3和test4的实现,并且类A和B并无任何的继承关系,并且A已经有一个父类了,那么现在想在类A中声明test3和test4,并且实现的方法功能也是和类B的test3和test4一毛一样,并且能直接通过类A的对象直接调用,那么,最好的方法就是直接调用类B的这两个方法,那么这不就是多继承了吗。而这个例子,能够用消息转发轻易的实现,而NSProxy刚好又是那个一步到位直接进入消息转发阶段的东西,因此这就是NSProxy出现的主要原因之一。
网友评论