Runtime
Runtime 是一套为c/c++/汇编提供运行时功能的api。
方法的本质探索
先上代码:
###Person类定义
@interface Person : NSObject
- (void)test_cat;
@end
@implementation Person
- (void)test_cat {
NSLog(@"%s",__func__);
}
@end
###main函数调用
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [[Person alloc] init];
[p test_cat];
}
return 0;
}
使用clang 命令,将main.m文件转化为C++文件mian.cpp
//1.先cd到目标路径
cd /Users/ricefun/Desktop/Runtime.demo
//2.1使用clang命令查看(具体版)
clang -rewrite-objc main.m -o mian.cpp
阅读mian.cpp文件
mian.cpp文件部分源码可以看到Person类的对象方法最终会转化为objc_msgSend()方法。
结论:方法的本质:
objc_msgSend
发送消息,其中id
为消息的接收者,sel
为方法的编号
objc_msgSend()
在objc_msgSend()为了找到函数实现过程中总的经历了两部分,方法查找
和消息转发机制
。
方法查找
方法查找又分为快速查找(汇编部分)
和慢速查找(C/C++部分)```
快速查找(汇编部分)
快速查找部分是用汇编写的,之所以这么做的原因有二:
1.汇编部分其实是在cache中找实现,既然是cache目的就是提高速度,不就要求快吗,而汇编是一种机器语言,运算速度快。
2.在C语言中不可能写一个函数来保留未知的参数并且跳转到任意一个韩函数。简单点:C语言不满足完成这件事的必要特性(能力)。
###汇编部分主要源码
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend
ENTRY _objc_msgLookup
UNWIND _objc_msgLookup, NoFrame
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LLookup_NilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LLookup_Nil
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LLookup_GetIsaDone:
CacheLookup LOOKUP // returns imp
#if SUPPORT_TAGGED_POINTERS
LLookup_NilOrTagged:
b.eq LLookup_Nil // nil check
// tagged
mov x10, #0xf000000000000000
cmp x0, x10
b.hs LLookup_ExtTag
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
b LLookup_GetIsaDone
LLookup_ExtTag:
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52, #8
ldr x16, [x10, x11, LSL #3]
b LLookup_GetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LLookup_Nil:
adrp x17, __objc_msgNil@PAGE
add x17, x17, __objc_msgNil@PAGEOFF
ret
END_ENTRY _objc_msgLookup
STATIC_ENTRY __objc_msgNil
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY __objc_msgNil
ENTRY _objc_msgSendSuper
UNWIND _objc_msgSendSuper, NoFrame
ldp p0, p16, [x0] // p0 = real receiver, p16 = class
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
END_ENTRY _objc_msgSendSuper
// no _objc_msgLookupSuper
ENTRY _objc_msgSendSuper2
UNWIND _objc_msgSendSuper2, NoFrame
ldp p0, p16, [x0] // p0 = real receiver, p16 = class
ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
CacheLookup NORMAL
END_ENTRY _objc_msgSendSuper2
ENTRY _objc_msgLookupSuper2
UNWIND _objc_msgLookupSuper2, NoFrame
ldp p0, p16, [x0] // p0 = real receiver, p16 = class
ldr p16, [x16, #SUPERCLASS] // p16 = class->superclass
CacheLookup LOOKUP
END_ENTRY _objc_msgLookupSuper2
.macro MethodTableLookup
// push frame
SignLR
stp fp, lr, [sp, #-16]!
mov fp, sp
// save parameter registers: x0..x8, q0..q7
sub sp, sp, #(10*8 + 8*16)
stp q0, q1, [sp, #(0*16)]
stp q2, q3, [sp, #(2*16)]
stp q4, q5, [sp, #(4*16)]
stp q6, q7, [sp, #(6*16)]
stp x0, x1, [sp, #(8*16+0*8)]
stp x2, x3, [sp, #(8*16+2*8)]
stp x4, x5, [sp, #(8*16+4*8)]
stp x6, x7, [sp, #(8*16+6*8)]
str x8, [sp, #(8*16+8*8)]
// receiver and selector already in x0 and x1
mov x2, x16
bl __class_lookupMethodAndLoadCache3
// IMP in x0
mov x17, x0
// restore registers and return
ldp q0, q1, [sp, #(0*16)]
ldp q2, q3, [sp, #(2*16)]
ldp q4, q5, [sp, #(4*16)]
ldp q6, q7, [sp, #(6*16)]
ldp x0, x1, [sp, #(8*16+0*8)]
ldp x2, x3, [sp, #(8*16+2*8)]
ldp x4, x5, [sp, #(8*16+4*8)]
ldp x6, x7, [sp, #(8*16+6*8)]
ldr x8, [sp, #(8*16+8*8)]
mov sp, fp
ldp fp, lr, [sp], #16
AuthenticateLR
.endmacro
简单的捋一下汇编部分:
- ENTRY _objc_msgSend
- 对消息接受者(id self,sel _cmd) 判断处理
-
- 查找isa
- 3.1 LNilOrTagged 找不到或者是TaggedPoint而类型
- 3.2 LGetIsaDone 找到了isa
- GetClassFromIsa_p16 找到当前Class
-
- 在CacheLookup 缓存表中找
- 5.1 CacheHit - 找到了那就调用 6(calls imp)
- 5.2 CheckMiss - 找不到就 7(__objc_msgSend_uncached)
- 5.3 add - 找到后,再添加到当前hash表中,为满足LRU原则
- calls imp
-
- objc_msgSend_uncached
- 7.1 MethodTableLookup方法表查找
- 7.2 __class_lookupMethodAndLoadCache3
- 7.3 lookUpImpOrForward 这个方法之后就是C/C++语言部分
慢速查找(C/C++部分)
慢速查找(C/C++部分),将以源码的形式展示,主要查看几个函数,关键代码都有注释,不在赘述。
lookUpImpOrForward()
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
//准备一些参数
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
//cache不一定是都是NO的,如果有cache == YES 会去查找一次
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
//线程锁,防止读写冲突
runtimeLock.lock();
//验证下是否是有效类
checkIsKnownClass(cls);
//判断下类是否实现,没有就实现下
if (!cls->isRealized()) {
realizeClass(cls);//给bits->data.rw&ro ...赋值
}
// 初始化配置类的一些参数
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
}
/*****以上都是一些准备工作,下面才是真正的查找过程*/
retry:
runtimeLock.assertLocked();
//再次去cache找一次
imp = cache_getImp(cls, sel);
if (imp) goto done;
//遵循继承链(类->父类->NSObject->nil)查找
//从 method lists 找
{//这里括号是为了形成局部作用域,防止命名冲突
Method meth = getMethodNoSuper_nolock(cls, sel);//看方法名字:不从父类中找,不就是从本身找吗
if (meth) {//如果找到了,就进行cache缓存
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// 找不到,就冲父类的cache和method lists找
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once.
//本类和父类都没有找到时,就会启动消息转发机制
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;
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
//内部实现[XXX xxx] unrecognized selector sent to instance逻辑
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
//线程锁解锁
runtimeLock.unlock();
//返回imp
return imp;
}
getMethodNoSuper_nolock() 函数
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
assert(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
//遍历方法list查找
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
//search_method_list()内部其实是用二分法查找
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
_objc_msgForward_impcache 汇编
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
//调用__objc_forward_handler方法
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
...
...
_objc_forward_handler
void *_objc_forward_handler = nil;
void *_objc_forward_stret_handler = nil;
#else
// 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;
objc_defaultForwardHandler
方法中就会展示我们日常熟悉的报错unrecognized selector sent to instance
以上就是方法慢速查找流程
消息转发机制
消息转发机制一般分为4步(也可分为3步):
- 1.动态方法决议(解析)
- 2.快速转发阶段
- 3.慢速转发阶段
- 4.消息无法处理报错
动态方法决议(解析)
方法在报错之前其实还进行了一段消息转发流程先看源码:
在上面的IMP lookUpImpOrForward()
方法中有这样一个方法:
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {//不是元类,调用Instance方法
_class_resolveInstanceMethod(cls, sel, inst);
}
else {//是元类,调用Class方法
// 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);
}
}
}
以class_resolveInstanceMethod
实例方法为例,程序就会对当前类发出一个resolveInstanceMethod()
方法,如果当前类重写过,就会进入处理,没有父类也有实现,那么最终会走到根父类NSObject
中默认实现, 返回NO
NSObject.mm
+ (BOOL)resolveClassMethod:(SEL)sel {
return NO;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO;
}
所以我们可以在重写的方法中添加方法,类似这样
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(eat)) {
IMP sleepImp = class_getMethodImplementation(self, @selector(sleep));
Method sleepMethod = class_getInstanceMethod(self, @selector(sleep));
const char *sleepType = method_getTypeEncoding(sleepMethod);
return class_addMethod(self, sel, sleepImp, sleepType);
}
return [super resolveInstanceMethod:sel];
}
特别注意:上面是以实例方法为例的如果是类方法,在走了
_class_resolveClassMethod
类方法后,发现没有做处理,还会走_class_resolveInstanceMethod
实例方法;即:
类方法如果找不到 - 动态方法决议
1.resolveClassMethod 处理了-->只注意元类2.resolveClassMethod 没有处理 --> resolveInstanceMethod - 类 - 元类 - NSObject
所以:可以在NSObject分类中重写resolveInstanceMethod类方法,处理所有方法;eg代码如下
#import "NSObject+MessageHandle.h"
#import "runtime.h"
@implementation NSObject (MessageHandle)
//全局防崩溃处理
+ (BOOL)resolveInstanceMethod:(SEL)sel {
//if一个个判断太麻烦
if (sel == @selector(say)) {
IMP sleepImp = class_getMethodImplementation(self, @selector(sleep));
Method sleepMethod = class_getInstanceMethod(self, @selector(sleep));
const char *sleepType = method_getTypeEncoding(sleepMethod);
return class_addMethod(self, sel, sleepImp, sleepType);
}
// if (sel == @selector(sleep)) {
// ...
// }
//综合处理 rf_路由_事物
NSString *selString = NSStringFromSelector(sel);
if ([selString hasPrefix:@"rf_xxxxx"]) {//rf_是自定义方法开发
//1.bug收集
//2.异常处理
if ([selString hasPrefix:@"rf_home_xxxxx"]) {
RFRountTo(@"home");//跳转到首页
} else if ([selString hasPrefix:@"rf_mine_xxxxx"]) {
RFRountTo(@"mine");//跳转到我的
}
...
}
//当然一般容错不会在方法决议里处理,在这里处理会压缩开发者的处理空间,一般放在完整消息转发中(NSInvocake)中,即最后一步
return NO;
}
@end
快速转发阶段
当方法在动态决议中没有处理后,系统会调用forwardingTargetForSelector
方法尝试着转发给其他类来处理
instrumentObjcMessageSends(BOOL flag)
:调试程序堆栈流程开关;路径:/tmp/msgSends
#import "Animal.h"
#import "runtime.h"
@implementation Person
//自己没有;-- 那就交给其他对象,因为其他对象可能有
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(say)) {
//返回其他对象
return [Person alloc];//Person类有该方法
}
[super forwardingTargetForSelector:aSelector];
}
@end
慢速转发阶段
如果快速转发流程也没有处理,那么就会进入慢速转发流程-消息签名和完整的消息转发
//1.先方法签名:打包方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(say)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];//方法签名
}
return [super methodSignatureForSelector:aSelector];
}
//2.完整消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation {
//事情 - 事物 综合处理
SEL sel = [anInvocation selector];
if ([[Person alloc] respondsToSelector:sel]) {
[anInvocation invokeWithTarget:[Person alloc]];
}else {
[super forwardInvocation:anInvocation];
}
//当然你可以在这里做综合处理,类似于CTMediator
}
tips:在
methodSignatureForSelector
和forwardInvocation
之间系统还会调用一次resolveInstanceMethod
方法,进一步去匹配签名过程
消息无法处理报错
如果上述3步都没有处理,那么久会抛出异常[XXX xxx] unrecognized selector sent to instance
完整代码
#import "Person.h"
#import "runtime.h"
@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(say)) {
IMP sleepImp = class_getMethodImplementation(self, @selector(sleep));
Method sleepMethod = class_getInstanceMethod(self, @selector(sleep));
const char *sleepType = method_getTypeEncoding(sleepMethod);
return class_addMethod(self, sel, sleepImp, sleepType);
}
return [super resolveInstanceMethod:sel];
}
//自己没有;-- 那就交给其他对象,因为其他对象可能有
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(say)) {
//返回其他对象
return [Person alloc];//Person类有该方法
}
[super forwardingTargetForSelector:aSelector];
}
//1.先方法签名:打包方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(say)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];//方法签名
}
return [super methodSignatureForSelector:aSelector];
}
//2.完整消息转发
- (void)forwardInvocation:(NSInvocation *)anInvocation {
//事情 - 事物 综合处理
SEL sel = [anInvocation selector];
if ([[Person alloc] respondsToSelector:sel]) {
[anInvocation invokeWithTarget:[Person alloc]];
}else {
[super forwardInvocation:anInvocation];
}
}
@end
这个时候你再看这张图
消息转发流程图
面试题
1.isKindOfClass&isMemberOfClass
int main(int argc, const char * argv[]) {
@autoreleasepool {
BOOL re1 = [[NSObject class] isKindOfClass:[NSObject class]];
BOOL re2 = [[NSObject class] isMemberOfClass:[NSObject class]];
BOOL re3 = [[Person class] isKindOfClass:[Person class]];
BOOL re4 = [[Person class] isMemberOfClass:[Person class]];
NSLog(@"\nre1:%hhd\nre2:%hhd\nre3:%hhd\nre4:%hhd",re1,re2,re3,re4);
BOOL re5 = [[NSObject alloc] isKindOfClass:[NSObject class]];
BOOL re6 = [[NSObject alloc] isMemberOfClass:[NSObject class]];
BOOL re7 = [[Person alloc] isKindOfClass:[Person class]];
BOOL re8 = [[Person alloc] isMemberOfClass:[Person class]];
NSLog(@"\nre5:%hhd\nre6:%hhd\nre7:%hhd\nre8:%hhd",re5,re6,re7,re8);
}
return 0;
}
先思考五秒再看打印结果:
2020-02-11 15:44:25.843470+0800 Test[44114:837672]
re1:1
re2:0
re3:0
re4:0
2020-02-11 15:44:25.844011+0800 Test[44114:837672]
re5:1
re6:1
re7:1
re8:1
解析:
我们先看其方法定义
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
####
object_getClass((id)self)
这个方法中:如果self是对象,那返回类,如果self是类对象,那返回元类
1-4是类方法,5-8是对象方法
网友评论