前言

想要成为一名iOS开发高手,免不了阅览源码。以下是笔者在OC源码探究中梳理的一个小系列——类与方针篇,欢迎大家阅览指正,一起也希望对大家有所帮助。

  1. OC源码剖析之方针的创建
  2. OC源码剖析之isa
  3. OC源码剖析之类的结构解读
  4. OC源码剖析之办法的缓存原理
  5. OC{ a / G P Z源码剖析之办法的查找原理
  6. OC源码剖析之办法的解析与转发原理

OC中办法的调用是经过objc_msgSend(或objc_msgSendO 4 C $ C I vSuper,或objc_msgSend_stret,或objc_msgSendSuper_stret)函数,向调用者发送名为SEL的音讯,找到详细的函数地址IMP,从而履行该函数。假设找不到IM! 2 , g _ Y 5 8 CP,会进u P y g !行办法的解析,这相当于供给一次容错处理;办法解析1 ^ o之后,假设仍然找不到IMP,还有最后3 @ b & Q + ) d 2一次时机,那便是音讯的转发。

办法的查找流程尽在 OC源码剖析之办法的查找= J n z 4 . T 8原理 一文中,文接此文,本文将深l G A : N ] D (化剖析办法的解析( e U与转发。

下面进入正题。

需求留心的是,笔者用的+ X ? g – ;源码是 objc4-756.2。

1 办法的解析

办法的解析,即method resolver(又叫音讯的解析,也叫办法决议),其建立在办法的查找的失利结果上,进口源码如下:

    // 在【类...根类】的【缓存+办法列表】中都没B  Q找到I 6 QM@ ) SP,进行办法解析
if (resolver  &&  !n Y ;  FtriedResolver) {
runtimeLock.+ 3 n * q Ounlock();
resolveMethod(cls, sel, inst);
runtimeLock.lock();
// Don't cache the result; we don't hold t3 r | F 0 Nhe lock so it may have 
// changed already. Re-do the search from scratch instead.
triedResolv= a ler = YES;
goto retry;
}

它主要是调0 J 0 N用了resolveMethod函数。resolveMethod函数处理完毕之后,还要重新s $ K ( Q @履行一次retry(再走一遍办法的查找流程)。其中,triedResolverO j _ 8 R L这个变量使得音讯的解析只进行一次。

1.1 resolveMethod

且看resolveMethod函数源码:

static void resoI ` ZlveMethod(Class cls, SEL sel, iR B u @ *d inst)
{
runtimeLock H ` c f t dk.assertUnlocked();
assert(cls->isRealized()| Z Q - V /);
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resg 4 y ; u N & oolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel,t { 8 C inst,
NO/*initialize*/, YES/*cache*/, N) _ J e }O/*resolver*/))
{
resolveInstanceMethod(clsk ? % t l $ A 5, sel, inst);
}
}
}

这儿有两个分g ) ` { e 9支,主要是对cls做个是否元类的判别:

  • 不是元类,意味着y ` G [ . #调用的是实例办法,那么履行resolveInstanceMethod函数
  • 是元类,说明调用的是类办法,履行resolveClassMethon q m c H l I Nd函数g J W B ;,之后假设仍然没找到IMP,则再去履行? Y = J 1 3 0 ;resolveInstanceMethod函数;

先看实例办法的状况

1.2 实例办法解析

resolveInstanceMethod源码如下:

static void resolvG 5 F 4 G ?eInstanceMethod(Class cls, SEL sel, id inst)
{
runtimeLock.assertUnlocked();
assert(cls->isRealized());
if (! lookUpImpOrNil(c2 N 6 : ols->ISA(), SEL_resolveInstanceMethod,3 s ^ cls,
NO/Q p o D B t j | _*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// 假设你没有完成类办法 +(BOOL)resolveI6 r instanceMethod:(SEL)sel
// NSObject也有完成,所以一般不会走这I h 9 f
// 留心这( g ! 4儿传入的第一个参数是:8 6 ( * 8 J ucls->ISA(),也便是元类
returL a O An;
}
BOOL (*md z n s L r 1 A }sg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
// 调用类办法: +6 k 3 A X T s .(BOOL)resolveInstanceMethod:(SEL)sel
bool resolvI r t W ned = msg(cls, S^ n Z @ d JEL_resolveInstance[ ) q O 2 / & 2Method, sel);
// 再找一次imp(这次是sel,而不是resolveInstaf B s h V i !nceMi V ( 4 Dethod)
IE ^ H r k xMP imp = loW j g j H d ~ 3 lokUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
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 resoL C s u ; O p 5lver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] + [ / c returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLoggin_ P $ r [ q ,g(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->name5 H P h ( - H [ForLogging(), sel_getName(sel));
}
}
}

resolveInstanceMethod函数先后调用了两次lookUpImpOrNil

  • 第一次的调用是判别类(包含其父类,直至根类)是否完成了+(BOOL)resolveInstanceMethod:(SEL)sel类办法
    • SEL_resolveInstanceMethod相当于@selector(resolveInstanceMethod:)NSObject类中有完成这个类办法(回来的是NO,会影响是否打印),所以一般会接着往下走。
  • 第二次的调用的目的是检测是否有sel对应的IMP。假设你在+(BOOL)resolveF a * M ! k : G xInstanceMethod:(SEL)sel中增加了sel| ; B z函数地址IMh ^ Y A nP,此刻再次去查找这个IM3 # + + ?P就能找到。

留心到这两次调用中,resolver都是NO,因而在其调用lookUpImpOrForward时不会触发 音讯的解析t P r k Q ?,仅仅是从“类、父类、…、根类”的缓存中和办法列表中找IMP,没找到会触发 音讯转发

lookUpImpOrNil函数源码:

If M R 1 K S % + PMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool ini* / P   g r Ftialize, bo4 2 Xol cache, bool resolver)
{
IMP imp = loC { ` x 6okUpImpOrFo. N # H Z | U mrward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else rR N k Leturn imp;
}

这儿会判别IMP是否? b 7 Y是音E 1 p # E c / y l讯转发而来的,假设是F [ 8,就不回来。

1.3 类办法解析

类办法的解析首要是调用resolveClassMethod函数,其源码如下:

// 这儿的cls是元类,因为类办法存储在元+ H f A F
static void resoG U a I x YlveClassMethod(Cl= @ % A ] Dass cls, SEL sel, id inst)
{
runtimeLock.assertUnloc{ z ? q Aked();
assert(cls->isRealizedi ( 1 ] 2 5 3 p +());
asserg e st(cls->isMet| w k  1 F d .aClass());
if (! lookUpImpOrNil(cls, SEL_resz T s S b ]olveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/$ b 1 g M l*resolver*/))
{
// 假设你没有完成类办法 +(BOOv c w j b e R FL)resolveClassMethod:(SEL)sel
// NSOb: ; - b r vject也有完成,所以一般不会走这儿
// 留心这儿的第一个参数是cls,是元类
return;
}
Class nonmeta;
{
mutex_locker_tl { / lock(runtimeLock);
// 获取 元类的方针,即类。换句话说,nonmeta 也便是 inst
nonmeta = getMaybek M cUnrealizedNonM8 &  o 2etaClass(cls, inV Z P $ { 0st);
// +initZ = 6 g (iali$ h 5 L y @ Pze path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal(J 4 d {"nonmeta clasG q J 7 P n = K ;s %s (%p) unexpectedly not realized",
nonmeta->nameFoM S L X / H l J |rLogging(), nonmeta);
}
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(o j 9 E F ] d 8 =msg))objc_msgSend;
// 调用类办法: +(BOOL)resolveClassMethod:(SEL)sel
bool resolved = msg(nonmeta, SEL_resolveClassMethod, sel: v S 2);
// 再找一次imp
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
if (ret i osolved  &&  PrintResolvn V h 8 jing) {
ifG 1 G e N (imp) {
_o! v , % ` 1 + s .bjc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolv$ } & O x j S w Ded to %p",
cls->isMetaClass[ V U w ~ 5() ? '+' : '-',
cls->nameFY a G 7  $ + k lorLogging(), sel_gL b C Q FetN` A G h 1 % l D .ame(sel), imp);
}
else {
// MetY + O 2 zhod resolver didn't add anything?
_} b 6 S ` ;objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but& P Q no new implementation of %c[%s %s] was found",
cls->nameForLV W /ogging(), sel_ge1 q A . V q ( k :tName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}

你会发现,这个函数与resolveInstanceMethod函数大体相同,需求留心的是,这次判别类(包含其父类,直至根类)是否完成的是+(BOOL)resolveClassMethod:v n 1 i I #(SEL)sel类办法。

让咱们回忆一下resolveMethod函数对类办法的解析

// try [nonMetaClass resolveClassMethod:sel]
// and [cls r , 3 9 [ q o w $esolveInsta2 ? 1 } F @ , ` pnceMO E Cethod:sel]
resolvec O | C 0 mClassMethod(clW 3 7 x =s, sel, inst);
if ( E y q A i Q!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// 此刻的cls为元类,也便是 NSObject 调用 resolveInstanceMethod:
resolveInsz h 5 W k B {tanceMethod(cls, sel, ib f j i ! 4 b 6nst);
}

在经过resolveClasv ? % _sMe# Q 6 r * MthodQ O @的处理之后,假设仍然没有找到类办法的IMP,就会再次履行resolveInstanceMethod函数!不同于实例办法的是,此刻的cls是元类,因而msg(cls, SEL_resolveInstanceMethod, sel);即是向元类内部发送resolveInstanceMethod:音讯] + : M 2 – + w 8,也就意味着是根类调A ; u y U v zresolveInstanceMethod:办法(这次只能在根类的分类中补救了),一起o d ` ,缓存查找类办法的IMP仅发生在根元类和根类中,而办法列表中查找类办法的IMP则别离在“元类、元类的父类、…、根元类、根类”中进行。

简而言之,当咱们调用一个类办法时,假设在类中没有完成,一起在resolveClassMethod中也没有处理,那么最终会调用根类(NSObject)的同名实例办法

1.4 举个栗子

经过上述的剖析,信任大家对办法的解析有了必定的认知,下面咱们来整个简单的比如消化/ v w ~ m [ Z I一下。

@interface Person : NSObject
+ (void)personClassMethod1;
- (void)personInstanceMethod1;
@end
@implementation Person
@end

一个简单的PersonU ^ F v T类,里边别离有一个类办法和一个实例办法,可是都没有完成。

接着增加对这两个办法 a I e c的解析:

- (void)un( K r dimplementedMethod:(SEL)sel {
NSLog(@"没完成?没关系,绝不溃散");
}
+ (BOOL)resX M w * Z LolveInstanceMethod:(SEL)sel {
NSLog(@"动态实例办法解析:1 9 . ~ h%@", NSStringFromSelector(sel));
if (se9 I Q X / 2l == @selector(personInstanceMethod1)) {
IMP methodIMP = class_getMethodImplementation(sS q # b y y aelf, @selector(unimplementedMethod:));
Method method = class_getInstanceMethod(Person.class, @selector(unimplementedMethod:));
consr ~ : C I K 5t char *methodType = method_getTypeEncoding(method);
rv # V ? h c * I jeturn class_addMethod(Person.class, sel, methodIMP, methodType);
}
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClass7 c @ d HMethod:(SEL)sel {
NSLog(@"动态类办法解析:%@", NSStringFromSelector(sel));
if (sel == @selecti @ q %or(personClassMethod1)) {
IMP methodIMP = class_getMethodImplementation(self, @seln n T N M : q E =ector(unimplementedMethod:));
Method method = class_getInstanceMethod(Pez % * ^ z * P X zrson.claC f f G B D ! 2 )ss, @selector(unimplementedMethod:));
constt z _ p M 4 J 3 R char *methodType = method_getTypeEncodin= J 5 G C g g p Wg(method);
return class_addMethod(objc_getMetaClass("Person"), sel, methodIMP, metM B ! r $hodType)g # G b;
}
return [super resolveClassMethod:sel];
}

看看打印:

经过对类办法解析的源码剖析,咱们知道,也能够把对Person类办法的处理放在NSObject分类的resolveClassMethN + Dod:resolveInstanceMethod:中,都能到达相同的效果(记得把Person类中的resolveClassMethod:处理去掉)。这儿略过不提。

2 音讯转发

办法的调用经过了查找、解t : 2l ] e n u r,假设仍是没有找到IMP,就会来到音讯转发流程。它的进口在lookUpImpOrForward函数靠后的方位

/^ 1 U u `/ No implementatiou k ` R = i 9 - Qn found, and method resolver didn't help.     
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sP - a Yel, imp, inst);

2.1 音讯的转发开端和结束

_objc_msgForward_impcache是汇编函数,以W % oarm64架构为例,其源码如下:

STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b	__obl q ( w ;jc_msgForward
END_ENTRY __objc_msgForward_impcache

__objc_msg. L v , ( M 7 zForward_impcache内部调用了__objc_msgForward

ENTRY& ] 6 __o* e a Ubjc_msgForward
a] t A Y W 1drp	x17, __objc_forward_handler@PAGE
ldr	p17, [x17] . } p O & 6 1, __objc_forward_handler@PAGEOFp ` & t { % E iF]
TailCallFu@ z C [ jnctionPointer x17
END_ENTRY __objc_msgForwardT B  n * & U =

这个函数主要做的事情是,经过页地址与页地址偏移的办法,拿到_objc_forward_handler函数的地址并调用。

说明:

  • adrp是以页为单位的大范围的地址读取指令,这儿的p便是page的意思
  • lW % - = f U gdru m ; ^ 0 9 x类似与movmvn,当立即数(__objc_msgFk W 0 s w : X corward中是[x17, __objc_forward_C | L ; Jhandler@PAGEOFU _ x h % Y & L CF]PAGEOFF是页地址偏移值)大于movmvn能操作的最大数时,就使用ldr

OBJC2中,] 6 D L O p _objc_forward_handler实际上便是objc_defaultForwardHz V ` v Xandler函数,其源码如下:

// Default forward handler halts the process.
__attribute__((noreturn)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent2 U Q 3 w Z i to instance %p "
"(no message forward handler is installed)",
c$ B e s  8 /lass_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_n Y : :objc_forward_handler = (void*)objc_defaultForwaR , lrdHandlerC L Q - n m Q { d;

是不是很熟悉?当咱们调用一个没完成的办法时,报的错便是“unrecognized selector sent to …”

可是问题来了,说好的音讯转发流程呢?这才刚开端怎么就结束了?不急,憋慌,且看下G $ 4 X b z去。

2.2 音讯转发的调用栈

回忆办法解析时举的比如,无妨把解n e P H K _ . ]析的内容u L 5去掉,Let it crash!

发现在溃散之前与音讯转发相关的内容是,调用了_CF_forwarding_prep_0___forwarding___这两个函数。遗憾的是这两个函数并未开源。

既然溃散信I z v f | ( z [息不能供给帮助,只好打印详细的调用信息了。

在办法的查找流程中,lo. x F i # + ! @ dg_and_fill_cache! / d U W * `数就跟打印有关,盯梢其源码:

static void
log_and_p U % Dfill_cache(Class cls, IMP imp, SEL sel, id receiveA * * K C Nr, Clas$ R d As implementer)
{
#iu 7 9f SUPPORT_MESSAGE_LOGGING
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessag5 q 0 c v / a ~ _eSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(). 9 8 / | l u M,
sel);
if (!cacheIt) return;
}
#endif
cache_fill(cls, sel, imp, receiver);
}
boolt 9 r p N ` _ objcMsgLogEnabledE y w y { m = false;
// Define SUPPORT_MESSAGE: W g G h $ a _ q_LOGGING to enabz v $ * N rle NSObjCMessageLoggingEnabled
#if !TARGET_OS_OSX
#   definM : T I i Ie SUPPORT_MESSAGE_LOGGING 0
#else
#   define SUPPORT_V F 2 + UMESSAGE_LOGGING 1
#endif

打印的要害函数便是logMessageSendQ C (,可是它受SUPPORT_MESSAGE_LOGGINGo? % & + f @ -bjcMsgLogEnabled控制。

继续跟进SU| 0 t ` WPPORT_MESSAGE_LOGGE , K 0 SING

#if !DYNAMIC_TAX 4 E / w . ~ % %RGETS_ENABLED
#defin@ i } % ; ` M #e TARGET_OS_OSX               1
...
#endif
#ifndef DYNAMIC_TARGETS_ENABLED
#define DYNAMIC_TARGETS_ENAB* j 7 j VLED   0
#endif

从源码不难看出TARGET_OS_OSX的值是1,因而,SUPPORT_MESSAGE_LOGGING也为1!

假设能把objcMsgLogEnabled改成true,明显就能够打印调用信息了。经. D @ w过大局搜索objcMsgLogEnabled,咱们找到了instrumentObjcMessageSends这个要害函数

void instrumentObjcMessageSends(BOOL flag)
{
bool enabl* R M M r D /e = flag;
// Shortcut NOP
if (objcMsgLogEnabled == enable)
return;
// If enabling, flush all method caches so we get so? M 0 X % A ome traces
if (enable)
_objc_flush_caches(Nil);
// Sync our log file
if (obj: 3 m # rcMsgLogFD != -1)
fsync (objcMsgLogFD);
objcMsgLogEnabled = enable;
}

接下% 4 s l来就好办了!来到maiK / t !n.m,增加以下代码

extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[])u U h o % {
@autoreleasepool {
instrumentObjcMessageSends(true);
[Person personClassMethod1];
insR 3 7 G I R | | ;trum/ ! Y 1 = E ( # bentObjcMessageSends(false);
}
return 0;
}

运转工程,直到再次溃散。此刻已打印函数调用栈,日志文件方位在logMessageSend函数中有标示

    // Create/open the log file
if? 1 X 5 _ (objcMsgLogFD == (-1))U 8 b X e % h L
{
snu E ] ! tprintf (buf, sizeof(buf), "/tmp/) 8 G e P b 7 qmsgSends-%d", (int) getpid (t # i P ) 5 J *));
objcMsgLogFD = secure_open (buf, O_WRONLY | O_CREAT, geteuid());
if (objcMsgLogFD < 0) {
// no log file - disable logging
oG _ S H -bjcMsg| E Y ` O 5 4 } PLogEnabled = false;
objcMsgLogFD = -1;
return true;
}
}

打开Finder(访达),cmd + shift + G快捷键,输入X x ( g x 2/tmp/msgSends,找到最新的一份日志文件(数字最大)

打印结果如下:

从这份日志能够看出,j } % s ?与转发相关的办法是forwardingTargetForSelectormethodSignatureForSelector,别离对应了音讯i v + 5 & O N [的快速转发流程和慢速转发流程,接下来开4 Z q端剖析这两个办法。

2.3 音讯的快速转发

forwardingTargetForSelector:对应的便是音讯的快速转发流程,它在源码中仅仅简单的回来nil(可在子类或分类中重写)

+] ! o G ~ ` ^ - Q (id)forwardingTargetFoM P WrSelector:(SEL)sel {
return nil;
}
- (id)forwardingTargetF- ` R W SorSelector:(SEL)sel {
return nil;
}

不过咱们能够L | p n j ~在开发文档中找到说L ( R I } & !明(cmd + shift + 0快捷键)

概括地说,forwardingTargetForSelector:主要是回来一个新的receiver,去处理sel这个当时类无法处理的音讯K v q z } w N z,假设处理不了,会转到效率低u A y m 1 q 4下的forwardInvocation:。在效率方面,forwardr l g BingTargetForSelector:抢先forwardInvocation:一个数量级,因而,最好不要用后者的办法处理音讯的转发逻辑。

关于forwardingTargetForSelectE n g 3 A | _ kor:回来的新的receiver,需求留心一下几点:

  • 肯定不能回来self,否则会陷入无限循环;
  • 不处理; t 6 # 8的话,能够回来nil,或许[super forwardingTargetForSelector:sel](非根类的状况),此刻会走methodSignatue j W /reForSelector:慢速转发流程;
  • 假设+ & # 6 t有这个receiver,此刻相当于履行objc_msgSend(newReceiver, sel, ...),那么它必须具有和被调用的办法相同办法签名的办法(办法名、参数列表、回来值类型都必须共同)。

2.3.1 举个栗子

咱们能够实验一下,准备工作如下

@interface ForwardObject : NSObject
@end
@implementation ForwardObject
+ (voh x J ~id)personClassMethod1 {
NSLog(@"类办法转发给%@,履行%s", [self className], __FUNCTION__);
}
- (void)personInstanceMethod1 {
NSLog(@"实例办法转发给%@,履行%s", [selfY q ^ w 1 o clasf { =sNae . Q g Y v ] z Eme], __FUNCTION__);
}
@end
@interface Person : NSObject
+ (voidG ` % W)personClassMethod1;
- (void)personInstanceMethod1;
@end
@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"实例办法I s L  A L w开端转发");
return [ForwardObject alloc];
}
+ (id)forwardingTargetForSelector:(SEL)sel {
NSLog(@"类办法开端转发");B 7 n ]
return [ForwardObject class];
}
@end

明显,FL ) -orwardObject作为音讯转发后的处理类,具有Person类的同名类办法和实例办法。现在开端验证,结果如下:

事实证明确实有用!接下来看音讯的慢速转发流程。

2.4 音讯的慢速w _ n C 8 ( 1转发

假设forwardingTaru d , D 9 / n Qge} 8 2tForSee v vlector:没有处理音讯(如回来nil),就会启动慢速转发流程,也便是methodSignatureForSelector:& , } (办法,相同需求在子类或分类中重写

// Replaced by CF (returns an NSMetho# m J IdSignature)
+ (NSMek r 1 ] - 9 .thodSignature *)methodSignatureForSeA ( & r k wlector:(SEL)sel {
_o% ? k = D ; 0 qbjc_fatal("+[NSObject me3 C 3thodSignatureForSelector:] "
"not available withouk k 3 ; t 7 ~ u +t CoreFoundation");
}
// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_od u Zbjc_fatal("-[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}

经过阅? | U览官方文档,咱们得出以下结论:

  • methodSignatureForSelector:办法是跟forwardInvocation:办法调配使用的,前者需求咱们根据sel回来一个办法签名,后者会把i k a这个办法签名封装成一个NSInvocation方针,并将其作为形参。
  • 假设有方针方针能处理Invocation中的selInvocation能够指使这个方针处理;否则不处理。
    • Invocation能够指使多个方针处Q N S j

留心:音讯的慢速转发流程功能较低,假设能够的话,你应该尽可能早地处理掉音讯( l 6 4 y C(如在办法解析时,或在音讯的快速转发流程时)。

2.4.1 举个栗子

针对慢速流程,相同能够验证。这儿把快速转发比如中H / 2 O T N f HPerson类修改一下:

@implementation Person
// MARK: 慢速转发--类办法
+ (NSMethodSignature *)methodSi # U YgnatureFor] ) ] y  1 &Selector:(SEL)aSelector {
NSLog(, @ . (@"类办法慢速转_ ; w b y发:%s, sel:%@", __FUNCTION__, NSStringFromSelector(aSelector));
if (aSeli n ; a m G =ector == @selek O c T ( m H N vctor(personClassMethod1)) {
return [NSMethodSig8 $ T q q K z c &nature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];3 L ] n Y 1 } 7
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL aSelector = [anInvocation selector];
NSLog(@"类办法慢速转发:%s,[ g : ? Z 1 s` r Gel:%@", __FUNCTION__, NSStringFromSelector(aSelector));
id target = [FoC t n Y 4 P BrwardObject class];
if ([target respondsToSelector:aSelector]) [anInvocation invokeWithTarget+ q =:target];
else [super forwardInvocation:anInvocation];
}
// MARK: 慢速转发--实例办法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)a& * ; wSelector {
NSLog(@"实例办法慢速转发:%s, sel:%@", __/ 4 = e g FUNCTION__, NSStringFW G _ - 0 iromSelector(aSelector));
if (aSelector == @selector(personInstanceMe{ B J B 2 v 6 6 *thod1)) {
retur1 r _n [NSMethodSignature signatureWithObjCTypes:"v@:"];P 9 9 S ` p N n w
}
re} - q - Z @ ^turn [super methodSignatureForSelector:aSelecto0 | q i * jr];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL aSelector = [anInvocation selector];
NSLog(@"实例` T ! l Y [办法慢速转发:%s, sel:%@", __FUNCTION__, NSS@ Y B ytringFromSelector(aSelector));
ForwardObject *obj = [ForX J 8 ?wardObjectT ( A alloc];
if ([obj respondsToSelector:aSelector]) [anInvocation invokeWithTarget:obj];
else [super forwardInvocatn : s L * n  H bion:anInvocation];
}
@end

其结果如下= w 5 9 (图所示,明显4 V Z G a也没有溃散。

对办y A Q Q } z # u法签名类型编码不熟悉的能够检查 苹果官方的类型编码介绍

3 总结

综上所述,当咱们调用办法时,首要进行办法H f & 8 , 2的查r & F找,假设查找失利,会进行办法的解析,此刻OC@ ~ ~ 0 9 # V o会给咱们一次对sel的处理时机,你能够在resolveInstaH M | :nceMethod:(类办法对应resolveClassMethod:)中G c I增加一个IMP;假设你没把握住这次时机,也便是解析失利时,会来到音讯转发阶段,这个阶段有两个时机去处理sel,别离是快B Y f %速转发的forwardingTargetForSelector:,以及慢速转发的mef H 5 p 4thodSignatureForSelector:。当然,假设这些时机你都抛弃了,那P R ~ f x * , tOC只好让程序溃散。

下面用一副图总结办法的解析和转发流程

4 问题评论

4.1 为什么( g | T y v * 5 5引入音讯转发机制?

在一个办法被调用之前,咱们是没办法确认它的完成地址的,直到运转时,这个办法被调用的时分,咱们才干真实知道它是否有完成,以及其详细的完成地址。这也便是所谓的“动态绑定”。

在编译期,假设编译器发现办法不存在,会直接报错;相同,在运转时,也有doesNotRecognizeSelector的处理。

在抛出doesNotRecognizeSelectorT / S个异常信息之前,OC利用其动态绑定的特性,引入了音讯转发机制,给予了咱们额定的时机处理o b w z # 5音讯(解析 or 转发),这样的做法明显更加周全合理。

5 PS

  • 源码工程已放到github上,请戳 objc4-756.2源码
  • 你也能够自行下载 苹果官方objc4源码 研究学习。
  • 转载请注明出处!