探索案例
/********对象声明*********/
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayNB;
@end
@interface OStudent : LGPerson
- (void)sayCode;
+ (void)sayGood;
@end
/********测试代码*********/
OStudent *s = [OStudent new];
[s sayCode]; //对象方法
[OStudent sayGood]; //类方法
使用clang命令把oc代码编译成c代码分析
clang -rewrite-objc main.m -o main.cpp
//经过整理,留下方法调用相关代码
OStudent *s = ((OStudent *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("OStudent"), sel_registerName("new"));
/*
objc_msgSend(s,sel_registerName("sayCode"))
*/
objc_msgSend((id)s, sel_registerName("sayCode"));
/*
objc_msgSend(objc_getClass("OStudent"),sel_registerName("sayGood"))
*/
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("OStudent"), sel_registerName("sayGood"));
- 都将
objc_msgSend强转为(void (*)(id, SEL))(void *),用于适应发送消息的格式 - 这里的
objc_msgSend第一个参数是接收消息的对象,第二个参数是SEL sel_registerName函数的作用是向runtime注册一个方法名;如果方法名已经注册,则放回已经注册的SEL。- 经过观察
对象方法和类方法的区别在于接收者不同 - 这里也说明了
对象方法和类方法本质上是没有区别的,区别在于存储的位置不同
objc_msgSend初探
objc_msgSend就是消息发送的实现API,通过源码搜索发现objc_msgSend的具体实现是通过汇编完成,OC中所有的消息发送都会调用该方法,这样也对方法执行速度有了很高的要求,这应该是objc_msgSend选择使用汇编实现的原因。
使用小提示
objc_msgSend使用过程中,如果直接使用objc_msgSend就会报错
解决方法
- 将
objc_msgSend强转成(void (*)(id, SEL))(void *),((void (*)(id, SEL))(void *)objc_msgSend)(s, @selector(sayCode)); - 关闭内存检查

objc_msgSend梳理
源码中搜索objc_msgSend,
在源码中
objc_msgSend有几种架构的实现,这里拿最常见的arm64(objc-msg-arm64.s)举例。
objc_msgSend 的实现
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
// tagged
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]
adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
cmp x10, x16
b.ne LGetIsaDone
// ext tagged
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 LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
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
以上是 _objc_msgSend的主干代码,接下来进行分析到底干了什么。
objc_msgSend 流程分析
cmp p0, #0 // 判断p0是否为空,p0是第一个参数(消息接收对象)
#if SUPPORT_TAGGED_POINTERS // SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
SUPPORT_TAGGED_POINTERS在objc2的64位环境下是1,所以执行b.le LNilOrTaggedb.le汇编命令表示,前一个cmp命令对比值小于等于,那么执行标号,否则往下执行- 如果
消息接受者为Nil或者是tagged pointer,就会执行LNilOrTagged标记 - 继续往下执行说明
消息接受者是isa指针 GetClassFromIsa_p16对isa&ISA_MASK操作拿到isa指向对象的指针LGetIsaDoneisa处理完成执行的标记,之后进行方法的查找CacheLookup NORMAL
LNilOrTagged
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
// tagged
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]
adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
cmp x10, x16
b.ne LGetIsaDone
// ext tagged
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 LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
b.eq LReturnZero如果消息接受者为空就return 0;这也是给nil发送消息不会有任何反应的原因所在- 完成之后执行
LGetIsaDone标记,进行方法的查找CacheLookup NORMAL
CacheLookup
.macro CacheLookup
// p1 = SEL, p16 = isa
ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask
#if !__LP64__
and w11, w11, 0xffff // p11 = mask
#endif
and w12, w1, w11 // x12 = _cmd & mask
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
add p12, p12, w11, UXTW #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // double wrap
JumpMiss $0
.endmacro
CacheLookup是一个宏macroCacheLookup完成了在cache中查找IMP缓存,这种查找方式称之为快速查找CacheLookup有三个参数CacheLookup NORMAL|GETIMP|LOOKUP,这次传入的是NORMAL- 从
cache中查找有三种结果:CacheHit、CheckMiss、add
CacheHit 缓存命中IMP
#define NORMAL 0
#define GETIMP 1
#define LOOKUP 2
// CacheHit: x17 = cached IMP, x12 = address of cached IMP, x1 = SEL
.macro CacheHit
.if $0 == NORMAL
TailCallCachedImp x17, x12, x1 // authenticate and call imp
.elseif $0 == GETIMP
mov p0, p17
cbz p0, 9f // don't ptrauth a nil imp
AuthAndResignAsIMP x0, x12, x1 // authenticate imp and re-sign as IMP
9: ret // return IMP
.elseif $0 == LOOKUP
// No nil check for ptrauth: the caller would crash anyway when they
// jump to a nil IMP. We don't care if that jump also fails ptrauth.
AuthAndResignAsIMP x17, x12, x1 // authenticate imp and re-sign as IMP
ret // return imp via x17
.else
.abort oops
.endif
.endmacro
传入的是NORMAL就返回IMP
CheckMiss缓存未命中IMP
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
- 出入的是
NORMAL,未命中缓存执行的是__objc_msgSend_uncached
__objc_msgSend_uncached
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p16 is the class to search
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
__objc_msgSend_uncached是对没有命中缓存的处理未命中缓存接下来就是去查找对象中的方法列表MethodTableLookup
MethodTableLookup
.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
MethodTableLookup查找对象的方法列表- 一堆代码的是做准备工作和收尾工作,为
__class_lookupMethodAndLoadCache3函数做铺垫 __class_lookupMethodAndLoadCache3在汇编中并没有实现具体实现,实现是用c写的_class_lookupMethodAndLoadCache3
_class_lookupMethodAndLoadCache3
至此方法查找中的第一步快速查找已经结束,接下来的是慢速查找。
Super在方法调用中的作用
案例
@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayNB;
@end
@interface OStudent : LGPerson
- (void)sayCode;
+ (void)sayGood;
@end
@implementation LGPerson
- (void)sayHello {
NSLog(@"%s",sel_getName(_cmd));
}
+ (void)sayNB {
NSLog(@"%s",sel_getName(_cmd));
}
@end
@implementation OStudent
- (void)sayCode {
[super sayHello];
}
+ (void)sayGood {
[super sayNB];
}
@end
//调用代码
OStudent *s = [OStudent new];
[s sayCode];
[OStudent sayGood];
编译后的c代码后 sayGood和 sayCode的实现
//整理后的代码
struct __rw_objc_super {
struct objc_object *object;
struct objc_object *superClass;
};
static void _I_OStudent_sayCode(OStudent * self, SEL _cmd) {
__rw_objc_super
// 向父类发消息(对象方法)
struct __rw_objc_super t_super;
t_super.receiver = self;
t_super.super_class = class_getSuperclass(objc_getClass("OStudent"));
objc_msgSendSuper(&t_super, sel_registerName("sayHello"));
}
static void _C_OStudent_sayGood(Class self, SEL _cmd) {
//向父类发消息(类方法)
struct __rw_objc_super t_ClassSuper;
t_ClassSuper.receiver = [s class];
t_ClassSuper.super_class = class_getSuperclass(objc_getMetaClass("OStudent"));// 元类的父类
objc_msgSendSuper(&t_ClassSuper, sel_registerName("sayNB"));
}
- 使用
super调用父类方法会使用objc_msgSendSuper发送消息 objc_msgSendSuper第一个参数是一个结构体指针,第二个参数是SEL__rw_objc_super中成员object表示消息接收者,super_class表示父类对象
objc_msgSendSuper
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
objc_msgSendSuper查找父类对象方法缓存- 不需要处理
isa,只需要直接查找缓存CacheLookup NORMAL
总结
- OC 方法调用的本质就是通过
对象+SEL找到IMP,这种动态调用方法叫做消息发送 - 实现这部分功能的是
objc_msgSend家族的函数 - 方法查找分为
快速查找和慢速查找