咱们都知道OC语言具备一个运行时的能力, 而这个能力全是得益于Runtime.html
Runtime: 结合了一套汇编、C、C++混合写成的, 为咱们当前OC提供运行时功能. 是一套api.api
编译时: 主要是将语言的语法等翻译成机器能够识别的语言, 可以进行中间连接bash
运行时: 整个代码运行起来的时候, 把代码装载在内存中, 此时内存处于活跃状态.主要是为了检查和前面编译时的一些错误.app
在Runtime应用里面, 主要是有三种方式ide
经过clang来生成一下cpp函数
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
#import "MyTest.h"
void run() {
NSLog(@"%s", __func__);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyTest *test = [MyTest alloc];
[test sayHello];
run();
NSLog(@"%@----", test);
return 0;
}
复制代码
编译后:oop
#pragma clang assume_nonnull end
void run() {
NSLog((NSString *)&__NSConstantStringImpl__var_folders__l_vdk2zw41289f08gcw7n2499c0000gn_T_main_22ff9d_mi_0, __func__);
}
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
MyTest *test = ((MyTest *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyTest"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)test, sel_registerName("sayHello"));
run();
NSLog((NSString *)&__NSConstantStringImpl__var_folders__l_vdk2zw41289f08gcw7n2499c0000gn_T_main_22ff9d_mi_1, test);
}
return 0;
}
复制代码
从中能够得知: OC方法的本质就是经过objc_msgSend进行发送消息优化
参数 | 做用 |
---|---|
id | 消息接收者(test) |
sel | 方法编号(sayHello) |
经过id接收者去cache中去寻找key, 经过与上mask获得index, 再查找到imp方法实现ui
发送消息 | 实现 |
---|---|
对象方法 | objc_msgSend() (经过id对象查找sel) |
类方法 | objc_msgSend() (经过类查找sel) |
向父类发送实例方法 | objc_msgSendSuper()方法 |
向父类发送类方法 | objc_msgSendSuper()方法 |
经过汇编断点调试进入 msg_msgSend
在750源码中, 搜索 objc_msgSend, 找到 objc-msg-arm64.s 文件中. 找到 ENTRY _objc_msgSend
ENTRY _objc_msgSend //进入_objc_msgSend
UNWIND _objc_msgSend, NoFrame//没有窗口
cmp p0, #0 // nil check and tagged pointer check 对比当前的0号寄存器是否为空, 判断当前是否有接收者
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative) 若是为空, 跳转 LNilOrTagged
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached 开启缓存查找
复制代码
进入objc_msgSend方法, 进入到正常流程, 拿取到isa(p13)
对isa进行一次 GetClassFromIsa_p16 操做:
.macro GetClassFromIsa_p16 /* src */
#if SUPPORT_INDEXED_ISA
//此处用于watch os 使用
// Indexed isa
mov p16, $0 // optimistically set dst = src
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa //判断是否为nopointer isa
// isa in p16 is indexed
adrp x10, _objc_indexed_classes@PAGE
add x10, x10, _objc_indexed_classes@PAGEOFF
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
#elif __LP64__
// 64-bit packed isa
and p16, $0, #ISA_MASK
#else
// 32-bit raw isa
mov p16, $0
#endif
.endmacro
复制代码
拿到isa判断是否为nopint isa, 而后对其作位移操做. (从alloc中能够得知经过位移获取shiftcls. 能够经过蒙板获取) 由此能够得知此isa是一个优化过的isa
接下来
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached 开启缓存查找
复制代码
开启缓存中查找方法:
.macro CacheLookup
// p1 = SEL, p16 = isa
ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask 平移16个字节获得真正的cache_t
#if !__LP64__
and w11, w11, 0xffff // p11 = mask //32位系统
#endif
and w12, w1, w11 // x12 = _cmd & mask //key与上mask获得cache_hash(oc750源码中 cache_hash(cache_key_t key, mask_t mak) 中)
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT)) 拿到buckets
ldp p17, p9, [x12] // {imp, sel} = *bucket 拿到bucket中的imp sel
1: cmp p9, p1 // if (bucket->sel != _cmd) 判断是否匹配到缓存
b.ne 2f // scan more 没有命中缓存走2:
CacheHit $0 // call or return imp 命中方法缓存返回方法实现到$0
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0 没有找到缓存
cmp p12, p10 // wrap if bucket == buckets。对比bucket
b.eq 3f // 对比同样走3:
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 从新存储一份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
复制代码
没有命中缓存的处理:
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP //imp 是否为 NORMAL LOOKUP
cbz p9, LGetImpMiss
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
复制代码
.endmacro
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
...
...
复制代码
在cache_t 分析中知道方法是存储在类的bit中的rw 与 ro中
.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
复制代码
能够看到最终目标是 __class_lookupMethodAndLoadCache3. 全局搜索 class_lookupMethodAndLoadCache3(汇编默认在方法前加上 ‘’)
直接跳转到objc-msg-new.mm
/*********************************************************************** * _class_lookupMethodAndLoadCache. * Method lookup for dispatchers ONLY. OTHER CODE SHOULD USE lookUpImp(). * This lookup avoids optimistic cache scan because the dispatcher * already tried that. **********************************************************************/
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
复制代码
在C语言中不可能经过写一个函数来保留未知的参数而且跳转到一个任意的函数指针, C语言没有知足作这件事的必要特性. 另外一个缘由就是objc_msgSend必须足够快, 汇编是最接近机器语言的.