前言
前面我们已经探究了整个objc的方法查找流程,包括快速查找:查找本类的cache和慢速查找:查找当前类的methodlist和父类cache以及父类methodlist;如果经过快速查找和慢速查找都没有查到对应的信息,会怎么样呢?我们就在本文一起来探索下;
慢速查找回顾
慢速查找过程中会调用realizeAndInitializeIfNeeded_locked
- 慢速查找过程中会调用
realizeAndInitializeIfNeeded_locked,这个方法内部会调用callInitialize, 这里相当于是调用class的initialize方法, - 系统在启动过程中会调用
+load、+initialize、C++构造方法会延长pre-main阶段的启动时间; - 方法查找过程中
快速和慢速结合的方法查找
- 当前类通过查找
cache:快速查找不到方法时,会进入慢速查找:lookUpImpOrForward; - 当前类的慢速查找也找不到,就进入父类的快速查找:
cache_getImp; - 如果父类快速查找也找不到,则进入父类的慢速查找:
lookUpImpOrForward,并依次往父类的父类查找,直到父类为空时imp = forward_imp;
方法动态决议
单例的实现
- 通过将两个数值先按位与再按位异或,可以实现单例的效果;
if (slowpath(behavior & LOOKUP_RESOLVER)) {// 3 & 2 = 2
behavior ^= LOOKUP_RESOLVER; //2 ^ 2 异或 相同为0 不同为1; 执行完后behavior = 0,以后再也无法满足上面判断了,相当于单例了;
return resolveMethod_locked(inst, sel, cls, behavior);
}
unrecognized selector sent打印的来源
- 在慢速查找
lookUpImpOrForward中如果最终也找不到对应的方法就会进入imp = forward_imp;而const IMP forward_imp = (IMP)_objc_msgForward_impcache;最后在done_unlock中把_objc_msgForward_impcache返回出来; - 全局搜索
_objc_msgForward_impcache,在汇编中看到 这个方法的定义是跳转到__objc_msgForward; - 全局搜索
__objc_msgForward这个方法最终跳转的是TailCallFunctionPointer方法
STATIC_ENTRY __objc_msgForward_impcache
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
//
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
- 全局搜索
TailCallFunctionPointer最终发现是跳转$0,$0是上文的x17,X17是__objc_forward_handler@PAGE - 全局搜索
objc_forward_handler,在objc-runtime.mm找到void *_objc_forward_handler = (void*)objc_defaultForwardHandler; objc_defaultForwardHandler的最终实现如下,这也解释了为什么找不到方法的时候会打印unrecognized selector sent to instance %p " "(no message forward handler is installed)等信息
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
消息处理流程
- 当方法找不到时会进入系统会自动进入消息转发流程,如图所示
2.当方法找不到的时候会进入resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)方法;这个方法里面会执行resolveInstanceMethod方法,执行完resolveInstanceMethod方法后再次通过慢速查找去找缺失的方法;
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
//对象方法动态决议
resolveInstanceMethod(inst, sel, cls);
}
else {
//类方法动态决议
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
对象方法动态方法决议+resolveInstanceMethod:
注意:这里只针对与实例方法做动态决议
resolveInstanceMethod方法内部做了什么神奇的事情吗?为什么执行完resolveInstanceMethod方法后还会再次通过慢速查找去找缺失的方法呢?
resolveInstanceMethod方法内部会去检查当前类是否有@selector(resolveInstanceMethod:);这个OC+方法,如果有就调用,那么开发者就可以在@selector(resolveInstanceMethod:);这个OC+方法里面添加查找不到的方法;如果没有找到@selector(resolveInstanceMethod:);这个OC+方法就直接返回了;正常情况下不会返回,因为即便我们自己的类不实现@selector(resolveInstanceMethod:);这个OC+方法,NSObject类中也有兜底实现;- 调用
@selector(resolveInstanceMethod:);这个OC+方法后,还会再次用慢速查找查一遍缺失的方法现在有没有,并且做标记;
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
//从当前类中查找@selector(resolveInstanceMethod:)这个oc方法,找不到直接返回
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
//找到了@selector(resolveInstanceMethod:)这个oc方法就调用一次,开发者可以在这里做操作决定是否要动态添加缺失的方法
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
//这里主要是检查当发送了resolveInstanceMethod:后,缺失的方法是否可以找到了
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
//对缺失的方法做日志
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
- 至此,我们发现了消息转发流程的一个重要方法——动态方法决议:
+resolveInstanceMethod:;方法在查找不到的时候就会进入类中的+resolveInstanceMethod:方法,而开发者可以在对应的class中的+resolveInstanceMethod:类方法里面,根据需要为类添加缺失的实例方法;
类方法动态方法决议+resolveClassMethod(inst, sel, cls);
- 为什么objc在类方法缺失的时候调用了
resolveClassMethod(inst, sel, cls);而不是resolveInstanceMethod:呢; - 为什么要特殊处理呢?因为正常情况下缺失的方法都是写在对应的类中,但是类方法是存储在元类中的,我们没有办法拿到元类,所以这里要特殊处理下,所以要用
resolveClassMethod(inst, sel, cls);这个方法; - 我们可以在对应的
class中根据需要实现+resolveClassMethod:这个类方法,这样类方法在查找不到的时候就会进入类中的+resolveClassMethod:方法,而开发者可以在对应的class中的+resolveClassMethod:类方法里面,根据需要为类添加缺失的类方法;
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
//判断类中resolveClassMethod:这个oc方法是否存在;
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
通用动态方法决议
- 不管是类方法还是实例方法,本质上都是实例方法,只不过类方法是从元类中找实例方法,对象方法是从元类中找实例方法;
- 根据isa的走位图我们知道,不管是元类还是类,他们最终的父类都是NSObject;
- 我们可以通过向
NSObject中添加+resolveInstanceMethod:方法,这样不管是对象方法缺失还是类方法缺失,最终都会找到NSObject中的+resolveInstanceMethod:方法,一次搞定对象方法和类方法,完美!
动态方法决议使用场景
- 通过向
NSObject中添加category的形式,可以向NSObject中添加+resolveInstanceMethod:方法,可以监听到整个app所有的OC方法无法找到引起的崩溃并进行处理;
AOP&OOP
- 类似于在
NSObject中的添加动态方法决议这种形式与我们平时常用的面向对象开发OOP面向对象是不同的,我们一般称为这种面向切面的低侵入性的为AOP模式; - 由于
AOP是无侵入的,可以在一定程度上对调用者和被调用者进行接耦,更加的灵活; AOP同时也会带来性能开销,例如我们的方法动态决议,如果使用AOP则需要一直向上到NSObject中才能查找到+resolveInstanceMethod:,不可避免的会浪费性能;
oc方法log
objc在调用插入cache方法:log_and_fill_cache()时会调用logMessageSend()方法进行写入日志,日志输出的具体细节代码如下
bool logMessageSend(bool isClassMethod,
const char *objectsClass,
const char *implementingClass,
SEL selector)
{
char buf[ 1024 ];
// Create/open the log file
if (objcMsgLogFD == (-1))
{
snprintf (buf, sizeof(buf), "/tmp/msgSends-%d", (int) getpid ());//日志输出的位置
objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
if (objcMsgLogFD < 0) {
// no log file - disable logging
objcMsgLogEnabled = false;
objcMsgLogFD = -1;
return true;
}
}
// Make the log entry
snprintf(buf, sizeof(buf), "%c %s %s %s\n",
isClassMethod ? '+' : '-',
objectsClass,
implementingClass,
sel_getName(selector));
objcMsgLogLock.lock();
write (objcMsgLogFD, buf, strlen(buf));
objcMsgLogLock.unlock();
// Tell caller to not cache the method
return false;
}
logMessageSend()是否记录日志是由objcMsgLogEnabled这个变量来控制的,因此我们只需要修改objcMsgLogEnabled的值即可控制是否记录日志;- 通过全局搜索,发现
void instrumentObjcMessageSends(BOOL flag)方法可以修改objcMsgLogEnabled的值;如果我们想控制是否记录方法调用log,只需实现void instrumentObjcMessageSends(BOOL flag)方法即可; - 通过以下代码如下最终实现了控制日志是否输出:
#import <Foundation/Foundation.h>
#import "GCPerson.h"
// 慢速查找
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
GCPerson *person = [GCPerson alloc];
[GCPerson say];
NSLog(@"Hello, World!");
}
return 0;
}