OC底层原理08:消息流程分析之动态方法决议&消息转发

慢速查找中,对慢速查找未找到后的处理,并无作详细解析,由于其内容一样涉及了方法查找流程中很重要的知识点,因此单独写。html

动态方法决议

在慢速查找仍未找到结果时,并不会直接就报错unrecognized selector sent to instance。在报错以前,Runtime会给一次动态方法决议的机会。markdown

从以前慢速查找的lookUpImpOrForward源码最后app

if (slowpath(behavior & LOOKUP_RESOLVER)) {
 behavior ^= LOOKUP_RESOLVER;  return resolveMethod_locked(inst, sel, cls, behavior); } 复制代码

其中resolveMethod_locked就会进入动态方法决议的部分。ide

对象方法

首先看下resolveInstanceMethod对象方法动态决议源码实现函数

static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{  runtimeLock.assertUnlocked();  ASSERT(cls->isRealized());  SEL resolve_sel = @selector(resolveInstanceMethod:);   // lookup resolveInstanceMethod  if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {  // Resolver not implemented.  return;  }   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  IMP imp = lookUpImpOrNil(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));  }  } } 复制代码

其中最关键的部分在于:oop

SEL resolve_sel = @selector(resolveInstanceMethod:);

BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);

IMP imp = lookUpImpOrNil(inst, sel, cls);
复制代码
    1. 进行一次消息发送msg(cls, resolve_sel, sel);消息的接收者为原来的cls,消息的方法主体为@selector(resolveInstanceMethod:)
    1. 消息发送后,调用了lookUpImpOrNil,其实它还是慢速查找的方法调用

在查找不到对象方法时,系统会进行resolveInstanceMethod的消息发送,查找类cls中是否实现有该方法,假如代码中添加了resolveInstanceMethod:post

@interface LGPerson : NSObject
- (void)sayHello; @end  @implementation LGPerson + (BOOL)resolveInstanceMethod:(SEL)sel{  NSLog(@"对象方法决议");  return [super resolveInstanceMethod:sel];; } @end  LGPerson *person = [LGPerson alloc]; [person sayHello]; 复制代码

能够看到,在LGPerson中实现了resolveInstanceMethod函数,当消息查找不到,进行动态方法决议时,就会调用到LGperson中的resolveInstanceMethod。咱们能够在这一层,对没有找到的方法,作一些调整:ui

@interface LGPerson : NSObject
- (void)sayHello; - (void)sayBye; @end  #import <objc/message.h> @implementation LGPerson  - (void)sayBye{  NSLog(@"%s",__func__); }  + (BOOL)resolveInstanceMethod:(SEL)sel{  NSLog(@"对象方法决议");  if (sel == @selector(sayHello)) {  IMP imp = class_getMethodImplementation(self, @selector(sayBye));  Method sayByeM = class_getInstanceMethod(self, @selector(sayBye));  const char *type = method_getTypeEncoding(sayByeM);  return class_addMethod(self, sel, imp, type);  }  return [super resolveInstanceMethod:sel];; } @end  LGPerson *person = [LGPerson alloc]; [person sayHello]; 复制代码

resolveInstanceMethod动态方法决议中,当判断经过时,在LGPerson中class_addMethod添加了一个方法sayBye,即当查找不到sayHello时,动态添加个方法,让系统去查找这个新的方法sayByespa

类方法

首先看下resolveClassMethod源码:3d

static void resolveClassMethod(id inst, SEL sel, Class cls)
{  runtimeLock.assertUnlocked();  ASSERT(cls->isRealized());  ASSERT(cls->isMetaClass());   if (!lookUpImpOrNil(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 = lookUpImpOrNil(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));  }  } } 复制代码
能够看到,类方法的动态决议和对象方法雷同,一样是:
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);

IMP imp = lookUpImpOrNil(inst, sel, cls);
复制代码

原理基本同样,一样在LGPerson中实现resolveClassMethod

@interface LGPerson : NSObject
+ (void)say666; + (void)say999; @end  #import <objc/message.h> @implementation LGPerson + (void)say999{  NSLog(@"%s",__func__); }  + (BOOL)resolveClassMethod:(SEL)sel{  NSLog(@"类方法决议");  if (sel == @selector(say666)) {  IMP imp = class_getMethodImplementation(objc_getMetaClass("LGPerson"), @selector(say999));  Method say999M = class_getInstanceMethod(objc_getMetaClass("LGPerson"), @selector(say999));  const char *type = method_getTypeEncoding(say999M);  return class_addMethod(objc_getMetaClass("LGPerson"), sel, imp, type);  }  return [super resolveClassMethod:sel]; } @end [LGPerson say666]; 复制代码

类方法存在元类信息中,类方法至关于元类对象的对象方法。因此 class_getMethodImplementation(objc_getMetaClass("LGPerson")class_getInstanceMethod(objc_getMetaClass("LGPerson")


可是,源码中调用resolveClassMethod后,为何又要判断当找不到时,就去调用对象方法动态决议呢? 仍是要从isa走位图来看

类方法保存在元类信息中,对象方法保存在类信息中,在OC底层他们基本没有区别,+ - 来区分类方法和对象方法,只是在上层人为的区分。

根据元类的继承链,当慢速查找类方法一直找到根类NSObject仍找不到时,经过调用resolveClassMethod添加类方法后,是从根类NSObject中找不到的lookUpImpOrNil(inst, sel, cls)为空。由于会添加到根元类NSObject中去,因此须要调用resolveInstanceMethod


消息转发

当在动态方法决议此次机会中,返回NO,没有作调整或操做时,会进入到消息转发流程。 可是在动态方法决议后,没看到有关消息转发的方法,要该如何查看呢?

利用instrumentObjcMessageSends方法监控OC底层消息发送。

赋值objcMsgLogEnabled,能够认为是消息日志的开关。

@interface LGPerson : NSObject
- (void)sayHello; @end  @implementation LGPerson  @end  extern void instrumentObjecMessageSends(BOOL flag); instrumentObjecMessageSends(YES); [person sayHello]; instrumentObjecMessageSends(NO); 复制代码

日志文件中,当动态方法决议以后,调用了forwardingTargetForSelectormethodSignatureForSelector

消息快速转发

forwardingTargetForSelector就是消息快速转发的函数。当在类中找不到方法,就返回一个第一接收者来接盘。

代码看如何利用:

#import "LGTercher.h"
@interface LGPerson : NSObject - (void)sayHello; @end @implementation LGPerson - (id)forwardingTargetForSelector:(SEL)aSelector{  if ([NSStringFromSelector(aSelector) isEqualTo:@"sayHello"]) {  return [LGTercher alloc];  }  return [super forwardingTargetForSelector:aSelector]; } @end  @interface LGTercher : NSObject - (void)sayHello; @end @implementation LGTercher - (void)sayHello{  NSLog(@"%s",__func__); } @end  LGPerson *person = [LGPerson alloc]; [person sayHello]; 复制代码

此时就看到,sayHello的方法接收者变成了LGTercher。固然,当LGTercher中也没有sayHello方法时,一样会报错unrecognized selector..


消息慢速转发

当没有处理消息快速转发时,会进入到消息慢速转发methodSignatureForSelector。此时能够调整的就不仅是消息接收者了。而且须要同时实现- forwardInvocation:方法 须要在消息慢速转发methodSignatureForSelector方法中,返回一个NSMethodSignature方法签名对象。若是其中返回nil的话,慢速转发也就不会进入到forwardInvocation方法进行处理了。

NSInvocation中,能够处理方法的targetselector等,对查找不到的方法作统一调整。

慢速转发代码:

@interface LGPerson : NSObject
- (void)sayHello; @end @implementation LGPerson // 慢速转发 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{  // 返回"v@:" 详见Type Encodings  return [NSMethodSignature signatureWithObjCTypes:"v@:"]; }  - (void)forwardInvocation:(NSInvocation *)anInvocation{  LGTercher *t = [LGTercher alloc];  anInvocation.target = t;  anInvocation.selector = @selector(sayBye);  [anInvocation invoke]; } @end  @interface LGTercher : NSObject - (void)sayBye; @end @implementation LGTercher - (void)sayBye{  NSLog(@"%s",__func__); } @end  LGPerson *person = [LGPerson alloc]; [person sayHello]; 复制代码


到此消息流程就结束了,当慢速转发也没有作处理时,就会抛出unrecognized selector..

流程图:


推荐参考

动态决议&消息转发

Type Encodings

相关文章
相关标签/搜索