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

相關文章
相關標籤/搜索