欢迎阅读iOS探索系列(按序阅读食用效果更加)c++
上一篇文章讲了方法在底层是如何经过sel
找到imp
的,本文就将经过源码来研究“没有实现的方法在底层要经过多少关卡才能发出unrecognized selector sent to instance
并Crash
”,看完本文后你会明白程序崩溃也是一个很复杂的过程git
在动态方法决议源码中,FXSon
中有两个只声明未实现的方法,分别调用它们:github
- (void)doInstanceNoImplementation;
+ (void)doClassNoImplementation;
消息查找流程
部分再也不展开讲解,未实现方法
查找主要通过如下流程:缓存
isa
平移获得class
,内存偏移获得cache->buckets
查找缓存因为慢速流程调用的是lookUpImpOrForward(cls, sel, obj, YES/*initialize*/, NO/*cache*/, YES/*resolver*/)
,遍历父类无果后来到动态方法解析
bash
只有resolver
和triedResolver
知足条件下才会进入动态方法解析
post
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_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;
}
复制代码
动态方法解析按调用方法走不一样分支:ui
元类
的话说明调用类方法,走_class_resolveInstanceMethod
非元类
的话调用了实例方法,走_class_resolveInstanceMethod
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
复制代码
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, 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(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));
}
}
}
复制代码
①检查cls中是否有SEL_resolveInstanceMethod(resolveInstanceMethod)
方法编码
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;
}
复制代码
注意这里的lookUpImpOrForward
中的resolver
为NO,因此只会在本类和父类中查找,并不会动态方法解析
spa
但cls没有这个方法,其实根类NSObject
已经实现了这个方法(NSProxy
没有实现)3d
// 具体搜索 NSObject.mm
+ (BOOL)resolveClassMethod:(SEL)sel {
return NO;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO;
}
复制代码
②向本类发送SEL_resolveInstanceMethod
消息,即调用这个方法
③lookUpImpOrNil
再次查找当前实例方法imp,找到就填充缓存,找不到就返回
④结束动态方法解析
,回到lookUpImpOrForward
方法将triedResolver
置否并goto retry
从新查找缓存和方法列表
相较于实例方法,类方法就复杂多了
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
assert(cls->isMetaClass());
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_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(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));
}
}
}
复制代码
①_class_resolveClassMethod
进入
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
复制代码
②lookUpImpOrNil
查找SEL_resolveClassMethod(resolveClassMethod)
是否实现
③向非元类发送SEL_resolveClassMethod
消息(因为cls是元类,_class_getNonMetaClass(cls, inst)
获得inst
)
④lookUpImpOrNil
再次查找当前实例方法imp,找到就填充缓存,找不到就返回
⑤结束_class_resolveClassMethod
,lookUpImpOrNil
查找sel
的imp
,如有imp
则退出动态方法决议,若无则进入_class_resolveInstanceMethod
⑥检查cls中是否有SEL_resolveInstanceMethod(resolveInstanceMethod)
方法
⑦向本类发送SEL_resolveInstanceMethod
消息
⑧lookUpImpOrNil
再次查找当前实例方法imp,找到就填充缓存,找不到就返回
⑨结束动态方法解析
,回到lookUpImpOrForward
方法将triedResolver
置否并goto retry
从新查找缓存和方法列表
Objective-C提供了一种名为动态方法决议
的手段,使得咱们能够在运行时动态地为一个selector
提供实现,并在其中为指定的selector
提供实现便可——子类重写+resolveInstanceMethod:
或+resolveClassMethod:
从实例方法流程图
中能够看出,解决崩溃的方法就是resolveInstanceMethod
阶段添加一个备用实现
#import "FXSon.h"
#import <objc/message.h>
@implementation FXSon
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(doInstanceNoImplementation)) {
NSLog(@"——————————找不到%@-%@方法,崩溃了——————————", self, NSStringFromSelector(sel));
IMP insteadIMP = class_getMethodImplementation(self, @selector(doInstead));
Method insteadMethod = class_getInstanceMethod(self, @selector(doInstead));
const char *instead = method_getTypeEncoding(insteadMethod);
return class_addMethod(self, sel, insteadIMP, instead);
}
return NO;
}
- (void)doInstead {
NSLog(@"——————————解决崩溃——————————");
}
@end
复制代码
resolveClassMethod
阶段效仿解决实例方法崩溃,类方法
也能够往元类
中塞一个imp
(实例方法
存在类对象
中,类方法
存在元类对象
中)
#import "FXSon.h"
#import <objc/message.h>
@implementation FXSon
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(doClassNoImplementation)) {
NSLog(@"——————————找不到%@+%@方法,崩溃了——————————", self, NSStringFromSelector(sel));
IMP classIMP = class_getMethodImplementation(objc_getMetaClass("FXSon"), @selector(doClassNoInstead));
Method classMethod = class_getInstanceMethod(objc_getMetaClass("FXSon"), @selector(doClassNoInstead));
const char *cls = method_getTypeEncoding(classMethod);
return class_addMethod(objc_getMetaClass("FXSon"), sel, classIMP, cls);
}
return NO;
}
+ (void)doClassNoInstead {
NSLog(@"——————————解决崩溃——————————");
}
@end
复制代码
resolveInstanceMethod
阶段由于元类
的方法以实例方法
存储在根元类
中,因为元类
和根源类
由系统建立没法修改,因此只能在根元类
的父类NSObject
中,重写对应的实例方法resolveInstanceMethod
进行动态解析(isa走位图完美说明一切)
#import "NSObject+FX.h"
#import <objc/message.h>
@implementation NSObject (FX)
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if ([NSStringFromSelector(sel) isEqualToString:@"doClassNoImplementation"]) {
NSLog(@"——————————找不到%@-%@方法,崩溃了——————————", self, NSStringFromSelector(sel));
IMP instanceIMP = class_getMethodImplementation(objc_getMetaClass("NSObject"), @selector(doInstanceNoInstead));
Method instanceMethod = class_getInstanceMethod(objc_getMetaClass("NSObject"), @selector(doInstanceNoInstead));
const char *instance = method_getTypeEncoding(instanceMethod);
return class_addMethod(objc_getMetaClass("NSObject"), sel, instanceIMP, instance);
}
return NO;
}
- (void)doInstanceNoInstead {
NSLog(@"——————————解决崩溃——————————");
}
@end
复制代码
实例方法
能够重写resolveInstanceMethod
添加imp
类方法
能够在本类重写resolveClassMethod
往元类添加imp
,或者在NSObject分类
重写resolveInstanceMethod
添加imp
动态方法解析
只要在任意一步lookUpImpOrNil
查找到imp
就不会查找下去——即本类
作了动态方法决议,不会走到NSObjct分类
的动态方法决议NSObject分类
重写resolveInstanceMethod
添加imp
解决崩溃那么把全部崩溃都在NSObjct分类
中处理,加之前缀区分业务逻辑,岂不是美滋滋?错!
NSObjct分类
动态方法决议以前已经作了处理这也不行,那也不行,那该怎么办?放心,苹果爸爸已经给咱们准备好走路了!
lookUpImpOrForward
方法在查找类、父类缓存和方法列表以及动态方法解析后,若是尚未找到imp
那么将进入消息处理的最后一步——消息转发流程
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
复制代码
在汇编中发现了_objc_msgForward_impcache
,以下是arm64的汇编代码
最后会来到c++中_objc_forward_handler
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
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);
}
复制代码
再来看看崩溃信息,崩溃以前底层还调用了___forwarding___
和_CF_forwarding_prep_0
等方法,可是CoreFoundation库
不开源
在无从下手之际,只能根据前辈们的经验开始着手——而后在logMessageSend
找到了探索方向(lookUpImpOrForward
->log_and_fill_cache
->logMessageSend
)
经过方法咱们能够看到,日志会记录在/tmp/msgSends
目录下,而且经过objcMsgLogEnabled
变量来控制是否存储日志
bool objcMsgLogEnabled = false;
static int objcMsgLogFD = -1;
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;
}
复制代码
instrumentObjcMessageSends
能够改变objcMsgLogEnabled
的值
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;
}
复制代码
因此咱们能够根据如下代码来记录并查看日志(仿佛不能在源码工程中操做)
extern void instrumentObjcMessageSends(BOOL flag);
int main(int argc, const char * argv[]) {
@autoreleasepool {
FXSon *son = [[FXSon alloc] init];
instrumentObjcMessageSends(true);
[son doInstanceNoImplementation];
instrumentObjcMessageSends(false);
}
}
复制代码
访达
中shift+command+G
访问/tmp/msgSends
动态方法解析
和
doesNotRecognizeSelector崩溃
之间,就是
消息转发流程
——分为
快速流程forwardingTargetForSelector
和
慢速流程methodSignatureForSelector
forwardingTargetForSelector
在源码中只有一个声明,并无其它描述,好在帮助文档中提到了关于它的解释:
forwardInvocation:
方法进行处理objc_msgSend(forwardingTarget, sel, ...);
来实现消息的发送以下代码就是是经过快速转发解决崩溃——即FXSon
实现不了的方法,转发给FXTeacher
去实现(转发给已经实现该方法的对象)
#import "FXTeacher.h"
@implementation FXSon
// FXTeacher已实现了doInstanceNoImplementation
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(doInstanceNoImplementation)) {
return [FXTeacher alloc];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
复制代码
在快速流程找不到转发的对象后,会来到慢速流程methodSignatureForSelector
依葫芦画瓢,在帮助文档中找到methodSignatureForSelector
点击查看forwardInvocation
forwardInvocation
和methodSignatureForSelector
必须是同时存在的,底层会经过方法签名,生成一个NSInvocation
,将其做为参数传递调用NSInvocation
中编码的消息的对象(对于全部消息,此对象没必要相同)anInvocation
将消息发送到该对象。anInvocation
将保存结果,运行时系统将提取结果并将其传递给原始发送者
慢速流程
流程就是先methodSignatureForSelector
提供一个方法签名,而后forwardInvocation
经过对NSInvocation
来实现消息的转发
#import "FXTeacher.h"
@implementation FXSon
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s -- %@",__func__,NSStringFromSelector(aSelector));
if (aSelector == @selector(doInstanceNoImplementation)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSLog(@"%s ",__func__);
SEL aSelector = [anInvocation selector];
if ([[FXTeacher alloc] respondsToSelector:aSelector])
[anInvocation invokeWithTarget:[FXTeacher alloc]];
else
[super forwardInvocation:anInvocation];
}
@end
复制代码
有兴趣的小伙伴们能够看看Demo,加深对OC消息机制的理解和防崩溃的运用