objc_msgSend分析(1)-汇编部分

什么是runtime?

runtime 一套c/c++/汇编语言形成的api,为Objective-C提供运行时功能。

运行时:代码装载到内存里
编译时:系统把语法编译成机器可识别的语言,生成可执行文件。

runtime的使用方式:

  • Objective-C code: @selecetor()
  • NSObject api : NSSelectorFromString()
  • runtime api : sel_registerName()
void testCMethod() {
    NSLog(@"=====");
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *p = [[LGPerson alloc] init];
        [p run];
    }
    return 0;
}

使用clang指令对main文件进行编译,生成相应的cpp文件:

clang -rewrite-objc main.m -o main.cpp

文件如下:

void testCMethod() {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_nw_tqjtztpn1yq6w0_wmgdvn_vc0000gn_T_main_a049bb_mi_0);
}
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        LGPerson *p = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("run"));
        
        testCMethod();
    }
    return 0;
}

从上面的c++代码,我们可以看出

方法的本质: 就是通过objc_msgSend发送信息
objc_msgSend(id receiver, sel_registerName("方法名"))

objc_msgSend 有2个参数:

  • id类型 消息的接收者
  • sel 方法名称

  结合类的cache_t,我们可以得到,调用方法就是通过objc_msgSend发送消息,在当前类的cache_t里通过sel进行查找,如果找到直接返回执行;找不到则在类的class_data_bits_t的data()返回的数据里class_rw_t -> (class_ro_t *)ro -> (method_list_t)baseMethodList去查找方法,如果找到就写入缓存并且返回执行;找不到就去父类里找,同理找到就写入缓存并且返回执行。

当我们实现一个c函数的时候,编译文件是直接调用的。

对象方法:传入的参数是 对象、sel
类方法:传入的参数是 类、sel

objc_msgSend(objc_getClass("aClassName"), sel_registerName("sel"))

父类方法:objc_msgSendSuper
参数为指向父类的结构体指针、@selector()

// 向父类发送实例方法:
struct objc_super superCls;
superCls.receiver = s;
superCls.super_class = [LGPerson class];
objc_msgSendSuper(&superCls, @selector(sayHello));
// 向父类发送类方法
struct objc_super myClassSuper;
myClassSuper.receiver = [s class];
myClassSuper.super_class = class_getSuperclass(object_getClass([s class]));// 元类
// myClassSuper.super_class = object_getClass([s class]);// 元类
objc_msgSendSuper(&myClassSuper,sel_registerName("sayNB"));
struct objc_super {
    __unsafe_unretained _Nonnull id receiver;
    __unsafe_unretained _Nonnull Class super_class;
};
#endif

objc_msgSend汇编的实现

objc_msgSend是用汇编语言实现的,原因有二:
1、性能问题,汇编实现性能比较高,执行速度快(相比Objective-C、c、c++)
2、c语言不可能通过写一个函数来保留未知的参数并且跳转到一个任意的函数指针。c语言无法满足这个特性。

由于汇编的代码量比较多,就不贴出来了,流程如下2张图:

image
image

总结:

  objc_msgSend的查找流程分为2个部分:1、查找缓存的汇编部分;2查找方法的c流程。

  汇编部分主要实现的功能是我们通过方法的接收者和SEL在类的cache_t中寻找是否有缓存,如果缓存命中,直接返回,如果不能命中,通过c语言去类的bits的rw中的ro中的方法列表中查找。

tips: objc_getClass和object_getClass

  • Class objc_getClass(const chat *aClassName)
    传入是类名,返回类

  • Class object_getClass(id obj)
    取的是obj的isa指向。
    1、传入的obj可能是instance对象,class对象、meta-class对象
    2、返回值
    a:如果是instance对象,返回class对象
    b:如果是class对象,返回meta-class对象
    c:如果是meta-class对象,返回的是根类的meta-class对象

tips: 常见的汇编指令

指令名称 指令含义
ENTRY 用于指定汇编程序的入口点。一个程序至少有一个入口点,也可有多个入口点,但是在一个源文件中,最多只能有一个ENTRY。
END 在源文件结束处写上,表示源程序的结尾。
mov a,b 数据传送指令: a = b
ldr r1,=0x123456789 大范围的地址读取指令: r1=0x123456789
ldr r1 ,[r2,#4] 内存访问指令(当ldr后面没有=号时为内存读取指令:将内存地址为r2+4的数据读取到r1中,相当于C语言中的*操作
ldr r1,[r2],#4 将内存地址为r2的数据读取到r1中,再将地址加4,r2=r2+4
str r1 ,[r2,#4] 存储指令: 将r1的值存入地址为r2+4的内存中
str r1,[r2],#4 将r1的值存入地址为r2的内存中,再将地址加4,r2=r2+4
add r1,r2,#1 加指令: r1=r2+1
sub r1,r2,#1 减指令: r1=r2-1
mul r1,r2,#4 乘法指令:r1=r2*4
b,bl 相对跳转指令,区别在于bl除了跳转以外,还将返回地址(bl的下一条指令地址)保存在lr寄存器中
cmp 比较指令
ret 返回主程序
call 调用子程序,子程序以ret结尾
jmp 跳转
jz/je 相等则跳转
jne/jnz 不相等则跳转
OR 或运算/按位或
XOR 异或运算
NOT 取反
SHL 逻辑左移
SHR 逻辑右移
CBZ 比较(Compare),如果结果为零(Zero)就转移(只能跳到后面的指令)
CBNZ 比较,如果结果非零(Non Zero)就转移(只能跳到后面的指令)

你可能感兴趣的:(objc_msgSend分析(1)-汇编部分)