想要成为一名
iOS开发高手
,免不了阅读源码。如下是笔者在OC源码探索
中梳理的一个小系列——类与对象篇,欢迎你们阅读指正,同时也但愿对你们有所帮助。html
OC
中方法的调用是经过objc_msgSend
(或objc_msgSendSuper
,或objc_msgSend_stret
,或objc_msgSendSuper_stret
)函数,向调用者发送名为SEL
的消息,找到具体的函数地址IMP
,进而执行该函数。若是找不到IMP
,会进行方法的解析,这至关于提供一次容错处理;方法解析以后,若是依然找不到IMP
,还有最后一次机会,那就是消息的转发。git
方法的查找流程尽在 OC源码分析之方法的查找原理 一文中,文接此文,本文将深刻剖析方法的解析与转发。github
下面进入正题。缓存
须要注意的是,笔者用的源码是 objc4-756.2。架构
方法的解析,即method resolver
(又名消息的解析,也叫方法决议),其创建在方法的查找的失败结果上,入口源码以下:app
// 在【类...根类】的【缓存+方法列表】中都没找到IMP,进行方法解析
if (resolver && !triedResolver) {
runtimeLock.unlock();
resolveMethod(cls, sel, inst);
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
复制代码
它主要是调用了resolveMethod
函数。resolveMethod
函数处理完毕以后,还要从新执行一次retry
(再走一遍方法的查找流程)。其中,triedResolver
这个变量使得消息的解析只进行一次。ide
resolveMethod
且看resolveMethod
函数源码:函数
static void resolveMethod(Class cls, SEL sel, id inst) {
runtimeLock.assertUnlocked();
assert(cls->isRealized());
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
resolveInstanceMethod(cls, sel, inst);
}
}
}
复制代码
这里有两个分支,主要是对cls
作个是否元类的判断:源码分析
resolveInstanceMethod
函数resolveClassMethod
函数,以后若是依然没找到IMP
,则再去执行resolveInstanceMethod
函数;先看实例方法的状况post
resolveInstanceMethod
源码以下:
static void resolveInstanceMethod(Class cls, SEL sel, id inst) {
runtimeLock.assertUnlocked();
assert(cls->isRealized());
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// 若是你没有实现类方法 +(BOOL)resolveInstanceMethod:(SEL)sel
// NSObject也有实现,因此通常不会走这里
// 注意这里传入的第一个参数是:cls->ISA(),也就是元类
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
// 调用类方法: +(BOOL)resolveInstanceMethod:(SEL)sel
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// 再找一次imp(此次是sel,而不是resolveInstanceMethod)
IMP imp = lookUpImpOrNil(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 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
函数前后调用了两次lookUpImpOrNil
:
+(BOOL)resolveInstanceMethod:(SEL)sel
类方法
SEL_resolveInstanceMethod
至关于@selector(resolveInstanceMethod:)
,NSObject
类中有实现这个类方法(返回的是NO
,会影响是否打印),因此通常会接着往下走。sel
对应的IMP
。假如你在+(BOOL)resolveInstanceMethod:(SEL)sel
中添加了sel
的函数地址IMP
,此时再次去查找这个IMP
就能找到。注意到这两次调用中,resolver
都是NO
,所以在其调用lookUpImpOrForward
时不会触发 消息的解析,仅仅是从“类、父类、...、根类”的缓存中和方法列表中找IMP
,没找到会触发 消息转发。
lookUpImpOrNil
函数源码:
IMP lookUpImpOrNil(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) {
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
复制代码
这里会判断IMP
是不是消息转发而来的,若是是,就不返回。
类方法的解析首先是调用resolveClassMethod
函数,其源码以下:
// 这里的cls是元类,由于类方法存储在元类
static void resolveClassMethod(Class cls, SEL sel, id inst) {
runtimeLock.assertUnlocked();
assert(cls->isRealized());
assert(cls->isMetaClass());
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// 若是你没有实现类方法 +(BOOL)resolveClassMethod:(SEL)sel
// NSObject也有实现,因此通常不会走这里
// 注意这里的第一个参数是cls,是元类
return;
}
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
// 获取 元类的对象,即类。换句话说,nonmeta 也就是 inst
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)resolveClassMethod:(SEL)sel
bool resolved = msg(nonmeta, SEL_resolveClassMethod, sel);
// 再找一次imp
IMP imp = lookUpImpOrNil(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 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));
}
}
}
复制代码
你会发现,这个函数与resolveInstanceMethod
函数大致相同,须要留意的是,此次判断类(包括其父类,直至根类)是否实现的是+(BOOL)resolveClassMethod:(SEL)sel
类方法。
让咱们回顾一下resolveMethod
函数对类方法的解析
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// 此时的cls为元类,也就是 NSObject 调用 resolveInstanceMethod:
resolveInstanceMethod(cls, sel, inst);
}
复制代码
在通过resolveClassMethod
的处理以后,若是依然没有找到类方法的IMP
,就会再次执行resolveInstanceMethod
函数!不一样于实例方法的是,此时的cls
是元类,所以msg(cls, SEL_resolveInstanceMethod, sel);
便是向元类内部发送resolveInstanceMethod:
消息,也就意味着是根类调用resolveInstanceMethod:
方法(此次只能在根类的分类中补救了),同时缓存查找类方法的IMP
仅发生在根元类和根类中,而方法列表中查找类方法的IMP
则分别在“元类、元类的父类、...、根元类、根类”中进行。
简而言之,当咱们调用一个类方法时,若是在类中没有实现,同时在resolveClassMethod
中也没有处理,那么最终会调用根类(NSObject
)的同名实例方法。
经过上述的分析,相信你们对方法的解析有了必定的认知,下面咱们来整个简单的例子消化一下。
@interface Person : NSObject
+ (void)personClassMethod1;
- (void)personInstanceMethod1;
@end
@implementation Person
@end
复制代码
一个简单的Person
类,里面分别有一个类方法和一个实例方法,可是都没有实现。
接着添加对这两个方法的解析:
- (void)unimplementedMethod:(SEL)sel {
NSLog(@"没实现?不要紧,毫不崩溃");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"动态实例方法解析:%@", NSStringFromSelector(sel));
if (sel == @selector(personInstanceMethod1)) {
IMP methodIMP = class_getMethodImplementation(self, @selector(unimplementedMethod:));
Method method = class_getInstanceMethod(Person.class, @selector(unimplementedMethod:));
const char *methodType = method_getTypeEncoding(method);
return class_addMethod(Person.class, sel, methodIMP, methodType);
}
return [super resolveInstanceMethod:sel];
}
+ (BOOL)resolveClassMethod:(SEL)sel {
NSLog(@"动态类方法解析:%@", NSStringFromSelector(sel));
if (sel == @selector(personClassMethod1)) {
IMP methodIMP = class_getMethodImplementation(self, @selector(unimplementedMethod:));
Method method = class_getInstanceMethod(Person.class, @selector(unimplementedMethod:));
const char *methodType = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("Person"), sel, methodIMP, methodType);
}
return [super resolveClassMethod:sel];
}
复制代码
看看打印:
经过对类方法解析的源码分析,咱们知道,也能够把对Person
类方法的处理放在NSObject
分类的resolveClassMethod:
或resolveInstanceMethod:
中,都能达到相同的效果(记得把Person
类中的resolveClassMethod:
处理去掉)。这里略过不提。
方法的调用通过了查找、解析,若是仍是没有找到IMP
,就会来到消息转发流程。它的入口在lookUpImpOrForward
函数靠后的位置
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
复制代码
_objc_msgForward_impcache
是汇编函数,以arm64
架构为例,其源码以下:
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
复制代码
__objc_msgForward_impcache
内部调用了__objc_msgForward
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
复制代码
这个函数主要作的事情是,经过页地址与页地址偏移的方式,拿到_objc_forward_handler
函数的地址并调用。
说明:
adrp
是以页为单位的大范围的地址读取指令,这里的p
就是page
的意思ldr
相似与mov
和mvn
,当当即数(__objc_msgForward
中是[x17, __objc_forward_handler@PAGEOFF]
,PAGEOFF
是页地址偏移值)大于mov
和mvn
能操做的最大数时,就使用ldr
。
在OBJC2
中,_objc_forward_handler
实际上就是objc_defaultForwardHandler
函数,其源码以下:
// Default forward handler halts the process.
__attribute__((noreturn)) 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);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
复制代码
是否是很熟悉?当咱们调用一个没实现的方法时,报的错就是“unrecognized selector sent to ...”
可是问题来了,说好的消息转发流程呢?这才刚开始怎么就结束了?不急,憋慌,且看下去。
回顾方法解析时举的例子,不妨把解析的内容去掉,Let it crash!
发如今崩溃以前与消息转发相关的内容是,调用了_CF_forwarding_prep_0
和___forwarding___
这两个函数。遗憾的是这两个函数并未开源。
既然崩溃信息不能提供帮助,只好打印具体的调用信息了。
在方法的查找流程中,log_and_fill_cache
函数就跟打印有关,跟踪其源码:
static void log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer) {
#if SUPPORT_MESSAGE_LOGGING
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cache_fill(cls, sel, imp, receiver);
}
bool objcMsgLogEnabled = false;
// Define SUPPORT_MESSAGE_LOGGING to enable NSObjCMessageLoggingEnabled
#if !TARGET_OS_OSX
# define SUPPORT_MESSAGE_LOGGING 0
#else
# define SUPPORT_MESSAGE_LOGGING 1
#endif
复制代码
打印的关键函数就是logMessageSend
,可是它受SUPPORT_MESSAGE_LOGGING
和objcMsgLogEnabled
控制。
继续跟进SUPPORT_MESSAGE_LOGGING
#if !DYNAMIC_TARGETS_ENABLED
#define TARGET_OS_OSX 1
...
#endif
#ifndef DYNAMIC_TARGETS_ENABLED
#define DYNAMIC_TARGETS_ENABLED 0
#endif
复制代码
从源码不难看出TARGET_OS_OSX
的值是1,所以,SUPPORT_MESSAGE_LOGGING
也为1!
若是能把objcMsgLogEnabled
改为true
,显然就能够打印调用信息了。经过全局搜索objcMsgLogEnabled
,咱们找到了instrumentObjcMessageSends
这个关键函数
void instrumentObjcMessageSends(BOOL flag) {
bool enable = flag;
// Shortcut NOP
if (objcMsgLogEnabled == enable)
return;
// If enabling, flush all method caches so we get some traces
if (enable)
_objc_flush_caches(Nil);
// Sync our log file
if (objcMsgLogFD != -1)
fsync (objcMsgLogFD);
objcMsgLogEnabled = enable;
}
复制代码
接下来就好办了!来到main.m
,添加如下代码
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
instrumentObjcMessageSends(true);
[Person personClassMethod1];
instrumentObjcMessageSends(false);
}
return 0;
}
复制代码
运行工程,直到再次崩溃。此时已打印函数调用栈,日志文件位置在logMessageSend
函数中有标注
// 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;
}
}
复制代码
打开Finder
(访达),cmd
+ shift
+ G
快捷键,输入/tmp/msgSends
,找到最新的一份日志文件(数字最大)
打印结果以下:
从这份日志能够看出,与转发相关的方法是forwardingTargetForSelector
和methodSignatureForSelector
,分别对应了消息的快速转发流程和慢速转发流程,接下来开始分析这两个方法。
forwardingTargetForSelector:
对应的就是消息的快速转发流程,它在源码中只是简单的返回nil
(可在子类或分类中重写)
+ (id)forwardingTargetForSelector:(SEL)sel {
return nil;
}
- (id)forwardingTargetForSelector:(SEL)sel {
return nil;
}
复制代码
不过咱们能够在开发文档中找到说明(cmd
+ shift
+ 0
快捷键)
归纳地说,forwardingTargetForSelector:
主要是返回一个新的receiver
,去处理sel
这个当前类没法处理的消息,若是处理不了,会转到效率低下的forwardInvocation:
。在效率方面,forwardingTargetForSelector:
领先forwardInvocation:
一个数量级,所以,最好不要用后者的方式处理消息的转发逻辑。
关于forwardingTargetForSelector:
返回的新的receiver
,须要注意一下几点:
self
,不然会陷入无限循环;nil
,或者[super forwardingTargetForSelector:sel]
(非根类的状况),此时会走methodSignatureForSelector:
慢速转发流程;receiver
,此时至关于执行objc_msgSend(newReceiver, sel, ...)
,那么它必须拥有和被调用的方法相同方法签名的方法(方法名、参数列表、返回值类型都必须一致)。咱们能够实验一下,准备工做以下
@interface ForwardObject : NSObject
@end
@implementation ForwardObject
+ (void)personClassMethod1 {
NSLog(@"类方法转发给%@,执行%s", [self className], __FUNCTION__);
}
- (void)personInstanceMethod1 {
NSLog(@"实例方法转发给%@,执行%s", [self className], __FUNCTION__);
}
@end
@interface Person : NSObject
+ (void)personClassMethod1;
- (void)personInstanceMethod1;
@end
@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"实例方法开始转发");
return [ForwardObject alloc];
}
+ (id)forwardingTargetForSelector:(SEL)sel {
NSLog(@"类方法开始转发");
return [ForwardObject class];
}
@end
复制代码
显然,ForwardObject
做为消息转发后的处理类,拥有Person
类的同名类方法和实例方法。如今开始验证,结果以下:
事实证实确实有效!接下来看消息的慢速转发流程。
若是forwardingTargetForSelector:
没有处理消息(如返回nil
),就会启动慢速转发流程
,也就是methodSignatureForSelector:
方法,一样须要在子类或分类中重写
// Replaced by CF (returns an NSMethodSignature)
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("+[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}
// Replaced by CF (returns an NSMethodSignature)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
_objc_fatal("-[NSObject methodSignatureForSelector:] "
"not available without CoreFoundation");
}
复制代码
经过阅读官方文档,咱们得出如下结论:
methodSignatureForSelector:
方法是跟forwardInvocation:
方法搭配使用的,前者须要咱们根据sel
返回一个方法签名,后者会把这个方法签名封装成一个NSInvocation
对象,并将其做为形参。Invocation
中的sel
,Invocation
能够指派这个对象处理;不然不处理。
Invocation
能够指派多个对象处理注意:消息的慢速转发流程性能较低,若是能够的话,你应该尽量早地处理掉消息(如在方法解析时,或在消息的快速转发流程时)。
针对慢速流程,一样能够验证。这里把快速转发例子中的Person
类修改一下:
@implementation Person
// MARK: 慢速转发--类方法
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"类方法慢速转发:%s, sel:%@", __FUNCTION__, NSStringFromSelector(aSelector));
if (aSelector == @selector(personClassMethod1)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL aSelector = [anInvocation selector];
NSLog(@"类方法慢速转发:%s, sel:%@", __FUNCTION__, NSStringFromSelector(aSelector));
id target = [ForwardObject class];
if ([target respondsToSelector:aSelector]) [anInvocation invokeWithTarget:target];
else [super forwardInvocation:anInvocation];
}
// MARK: 慢速转发--实例方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"实例方法慢速转发:%s, sel:%@", __FUNCTION__, NSStringFromSelector(aSelector));
if (aSelector == @selector(personInstanceMethod1)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL aSelector = [anInvocation selector];
NSLog(@"实例方法慢速转发:%s, sel:%@", __FUNCTION__, NSStringFromSelector(aSelector));
ForwardObject *obj = [ForwardObject alloc];
if ([obj respondsToSelector:aSelector]) [anInvocation invokeWithTarget:obj];
else [super forwardInvocation:anInvocation];
}
@end
复制代码
其结果以下图所示,显然也没有崩溃。
对方法签名类型编码不熟悉的能够查看 苹果官方的类型编码介绍
综上所述,当咱们调用方法时,首先进行方法的查找,若是查找失败,会进行方法的解析,此时OC
会给咱们一次对sel
的处理机会,你能够在resolveInstanceMethod:
(类方法对应resolveClassMethod:
)中添加一个IMP
;若是你没把握住此次机会,也就是解析失败时,会来到消息转发阶段,这个阶段有两个机会去处理sel
,分别是快速转发的forwardingTargetForSelector:
,以及慢速转发的methodSignatureForSelector:
。固然,若是这些机会你都放弃了,那OC
只好让程序崩溃。
下面用一副图总结方法的解析和转发流程
在一个方法被调用以前,咱们是没办法肯定它的实现地址的,直到运行时,这个方法被调用的时候,咱们才能真正知道它是否有实现,以及其具体的实现地址。这也就是所谓的“动态绑定”。
在编译期,若是编译器发现方法不存在,会直接报错;一样,在运行时,也有doesNotRecognizeSelector
的处理。
在抛出doesNotRecognizeSelector
这个异常信息以前,OC
利用其动态绑定的特性,引入了消息转发机制,给予了咱们额外的机会处理消息(解析 or 转发),这样的作法显然更加周全合理。
github
上,请戳 objc4-756.2源码