消息转发流程(上)
Posted WeaterMr
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了消息转发流程(上)相关的知识,希望对你有一定的参考价值。
消息转发流程(上)
补充
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
系统会制动把我们所有的initialize
方法自动调用,还有load
方法,c++
的构造函数。
分析一个崩溃场景
Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '+[Good number]: unrecognized selector sent to class 0x100008310'
当我们在调用某些方法时当方法没有实现时就会报这个错误。
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
}
当所有的方法查找不到时会给imp设置一个默认值 forward_imp
_objc_msgForward_impcache
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
#if !__OBJC2__
// Default forward handler (nil) goes to forward:: dispatch.
void *_objc_forward_handler = nil;
void *_objc_forward_stret_handler = nil;
#else
// Default forward handler halts the process.
__attribute__((noreturn, cold)) 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;
原来,当我们的方法找不到时,系统会返回一个默认的imp
,即当前的报错信息。当遇到报错,难道只能看着程序崩溃?是否还有补救的机会呢?看下面流程。
对象方法动态决议
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
return imp;
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// 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 = lookUpImpOrNilTryCache(inst, sel, cls);
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
系统自动发送消息IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
在表里继续查找- 即实现
resolveInstanceMethod
方法将不会报错,容错处理。
Good *good = [Good alloc];
[good name];
- (void)number{
NSLog(@"我执行了number方法");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(name)) {
IMP numberImp = class_getMethodImplementation(self, @selector(number));
Method method = class_getInstanceMethod(self, @selector(number));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel, numberImp, type);
}
NSLog(@"resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
2021-07-05 23:14:11.470286+0800[2309:90947] 我执行了number方法
如果我们没有实现resolveInstanceMethod
: 方法系统默认会实现。
类方法动态决议
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNilTryCache(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 = lookUpImpOrNilTryCache(inst, sel, cls);
}
流程和对象方法很相似,也是实现对应的resolveClassMethod
:
Good *good = [Good alloc];
[Good name];
// 元类以对象方法的方法
+ (BOOL)resolveClassMethod:(SEL)sel{
NSLog(@"resolveClassMethod :%@-%@",self,NSStringFromSelector(sel));
if (sel == @selector(name)) {
IMP age = class_getMethodImplementation(objc_getMetaClass("Good"), @selector(age));
Method method = class_getInstanceMethod(objc_getMetaClass("Good"), @selector(age));
const char *type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("Good"), sel, sayNBImp, type);
}
return [super resolveClassMethod:sel];
}
也可以对NSObjece添加一个扩展实现对应的resolveInstanceMethod
原理主要是根据isa的走位图来解释为什么只需要实现resolveInstanceMethod
。
#import "NSObject+me.h"
+ (BOOL)resolveInstanceMethod:(SEL)sel{
// resolveInstanceMethod :LGTeacher-say666 为什么是两次 家庭作业
// 处理 sel -> imp
NSLog(@"resolveInstanceMethod :%@-%@",self,NSStringFromSelector(sel));
if (sel == @selector(number)) {
IMP ageImp = class_getMethodImplementation(self, @selector(age));
Method method = class_getInstanceMethod(self, @selector(age));
const char *type = method_getTypeEncoding(method);
return class_addMethod(self, sel, ageImp, type);
}else if (sel == @selector(bad)) {
IMP badImp = class_getMethodImplementation(objc_getMetaClass("Good"), @selector(name));
Method method = class_getInstanceMethod(objc_getMetaClass("Good"), @selector(name));
const char *type = method_getTypeEncoding(method);
return class_addMethod(objc_getMetaClass("Good"), sel, badImp, type);
}
return NO;
}
通过NSObject添加分类
,可以处理所有的方法找不到的问题,也可以对方法进行分类,然后根据方法名字进行错误分析归类。日志上次,hook等操作。
什么是面向切面编程AOP
这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程
。
面向切面编程(AOP
是Aspect Oriented Program
的首字母缩写) ,我们知道,面向对象的特点是继承
、多态
和封装
。而封装就要求将功能分散到不同的对象中去,这在软件设计中往往称为职责分配。实际上也就是说,让不同的类设计不同的方法。这样代码就分散到一个个的类中去了。这样做的好处是降低了代码的复杂程度,使类可重用。
但是人们也发现,在分散代码的同时,也增加了代码的重复性。什么意思呢?比如说,我们在两个类中,可能都需要在每个方法中做日志。按面向对象的设计方法,我们就必须在两个类的方法中都加入日志的内容。也许他们是完全相同的,但就是因为面向对象的设计让类与类之间无法联系,而不能将这些重复的代码统一起来。
如果将段代码写在一个独立的类独立的方法里,然后再在这两个类中调用。这样会产生耦合了,抽出的类改变会影响使用的类。AOP
这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。一般而言,我们管切入到指定类指定方法的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。有了AOP
,我们就可以把几个类共有的代码,抽取到一个切片中,等到需要时再切入对象中去,从而改变其原有的行为。这样看来,AOP其实只是OOP的补充而已
。OOP
从横向上区分出一个个的类来,而AOP
则从纵向上向对象中加入特定的代码。有了AOP
,OOP
变得立体了。如果加上时间维度,AOP
使OOP
由原来的二维变为三维了,由平面变成立体了。从技术上来说,AOP
基本上是通过代理机制实现的。 AOP
在编程历史上可以说是里程碑式的,对OOP
编程是一种十分有益的补充。
以上是关于消息转发流程(上)的主要内容,如果未能解决你的问题,请参考以下文章