探索
探索案例
/********对象声明*********/
@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
的主干代码,接下来进行分析到底干了什么。
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 LNilOrTagged
-
b.le
汇编命令表示,前一个cmp
命令对比值小于等于,那么执行标号,否则往下执行 - 如果
消息接受者
为Nil
或者是tagged pointer
,就会执行LNilOrTagged
标记 - 继续往下执行说明
消息接受者
是isa指针
-
GetClassFromIsa_p16
对isa&ISA_MASK
操作拿到isa指向对象的指针 -
LGetIsaDone
isa处理完成执行的标记,之后进行方法的查找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
是一个宏macro
-
CacheLookup
完成了在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
家族的函数 - 方法查找分为
快速查找
和慢速查找