6、iOS底层分析 - 消息发送(objc_msgeSend)分析

调试分析

OC中的方法到底是怎么调用的呢?之前查资料知道oc的方法调用是通过消息发送的形式进行调用,那就来结合源码探究一下

     LGPerson *person = [[LGPerson alloc] init];
     Class pClass = [LGPerson class];
     [person sayNB]; 

汇编查看流程(注意使用真机调试)

Debug - Debug workflow - Always show Disassembly

    0x100000b4b <+27>:  movq   0x746(%rip), %rsi         ; (void *)0x00000001000012d8: LGPerson
    0x100000b52 <+34>:  movq   0x71f(%rip), %rcx         ; "alloc"
    0x100000b59 <+41>:  movq   %rsi, %rdi
    0x100000b5c <+44>:  movq   %rcx, %rsi
    0x100000b5f <+47>:  movq   %rax, -0x48(%rbp)
    0x100000b63 <+51>:  callq  *0x4a7(%rip)              ; (void *)0x0000000100344640: objc_msgSend
    0x100000b69 <+57>:  movq   0x710(%rip), %rsi         ; "init"
    0x100000b70 <+64>:  movq   %rax, %rdi
    0x100000b73 <+67>:  callq  *0x497(%rip)              ; (void *)0x0000000100344640: objc_msgSend
    0x100000b79 <+73>:  movq   %rax, -0x18(%rbp)
    0x100000b7d <+77>:  movq   0x714(%rip), %rax         ; (void *)0x00000001000012d8: LGPerson
    0x100000b84 <+84>:  movq   0x6fd(%rip), %rsi         ; "class"
    0x100000b8b <+91>:  movq   %rax, %rdi
    0x100000b8e <+94>:  callq  *0x47c(%rip)              ; (void *)0x0000000100344640: objc_msgSend
    0x100000b94 <+100>: movq   %rax, -0x20(%rbp)
    0x100000b98 <+104>: movq   -0x18(%rbp), %rax
    0x100000b9c <+108>: movq   0x6ed(%rip), %rsi         ; "sayNB"

通过汇编 Debug 可以发现LGPerson 调用的4个方法 alloc 、init、class、sayNB ,

其中init、sayNB 都可以看到 objc_msgSend。继续分析

找到 target 目录下,在终端使用clang -rewrite-objc main.m,编译生成main.cpp文件,对照二者的main函数

// oc
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [[LGPerson alloc] init];;
        [person sayNB];
    }
    return 0;
}

// c++
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        LGPerson *person = ((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)person, sel_registerName("sayNB"));
    }
    return 0;
}
  1. 对比分析可以发现,我们调用的 [person sayNB] 方法,在C++中则被编译成为了objc_msgSend((id)person, sel_registerName("sayNB"))
  2. 由以上两个地方可以知,方法在底层的本质其实就是 objc_msgSend  发送消息,调用方法其实就是调用  objc_msgSend 向特定的对象发送特定的消息
  3. objc_msgSend(id,sel)      id 消息接收者(对象)    sel 方法编号
  4. 得到的 key & mask 得到 index,然后找到对应 imp
  • 发送消息是找函数的过程,oc 底层封装的就是 c 就是通过消息发送找到真正函数实现的 imp
  • 如果直接调用 c 的函数,并不会有消息发送的过程,是直接调用的。
  • // 发送消息 : objc_msgSemd
    // 对象方法 - person - sel
    // 类方法  - 类 - sel
    // 父类 : objc_superMSgSend

control+in进入 进入objc_msgSend方法,找到其所在为libobjc.A.dylib,那么接下来,我们可以通过源码搜索对其进行探索

objc_msgSend 分析

在objc源码中全局搜索 objc_msgSend 方法,在 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
    // person - isa - 类
	ldr	p13, [x0]		// p13 = isa
	GetClassFromIsa_p16 p13		// p16 = class
LGetIsaDone:
	CacheLookup NORMAL		// calls imp or objc_msgSend_uncached

这个地方是用汇编写的,不是很好读。经过查询简单分析一下

  1. cmp    p0, #0            // nil check and tagged pointer check  空指针标记,也就是判空
  2. 判断  SUPPORT_TAGGED_POINTERS   操作(支持标记的指针)
  3. p13 相当于 isa
  4. 其次通过  GetClassFromIsa_p16  获取 当前的类 class
  5. 通过 CacheLookup 先到缓存中进行查找。
  6.  其实和类的结构基本上一样 ,就是通过汇编语言编写,对当前的类进行地地址偏移16个字节( isa 和 superClass 各占据8个字节),找到 cache 的结构体所在,然后找到 buckets 进行缓存方法的查找
  7. 如果没有查找到,则进行 CheckMiss
.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

CheckMiss

根据传递参数的不同,返回不同的结果

objc_msgSend 刚才传递的类型为 NORMAL,所以接下来会调用__objc_msgSend_uncached 方法,并且内部只调用了MethodTableLookup 的方法以及回调一个方法指针

    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
  • 类的结构。先通过isa找到类,再找到类的 cache 的 buckets 中 ,当缓存之中全都没有找到之后,那么接下来就去类原来的存储空间,也就是  class - bits - rw - ro - methodList  中去找方法的定义。
  • 消息发送的汇编也是一样的在 MethodTableLookup 方法中,先是做了一系列准备工作,之后调用了__class_lookupMethodAndLoadCache3 方法,进行方法查找和加载缓存,
  • 下面汇编结束是 C/C++
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 _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

注意 : 从汇编进入到C++阶段的方法,需要把汇编中的方法去掉一个下划线,才能在源码中搜索到。

  • __class_lookupMethodAndLoadCache3 之后就有些迷茫不知道下一步该干什么了,这个地方就查看了一下资料。
  • control+in 进入  objc_msgSend  方法之后,往下找发现有一个  __objc_msgSend_uncached  方法
  • 进入  __objc_msgSend_uncached  内部,在断点处,发现其跳转了一个位于  objc-runtime-new.mm  文件的C++方法

总结:

  • objc_msgSend 是使用汇编写的,主要是速度够快,够灵活(C语言做不到写一个函数来保留未知的参数并且跳转到任意的函数指针)
  • 汇编可以动态识别,性能更快更高。汇编更加容易被机器识别
  • objc_msgSend 首先通过快速路径对缓存进行查找,如果查找不到则进入 MethodTableLookup 方法列表查找,通过_class_lookupMethodAndLoadCache3 进行查找并且缓存


扩展

消息的发送
	LGStudent *s = [LGStudent new];
        [s sayCode];
        // 方法调用底层编译
        // 方法的本质: 消息 : 消息接受者 消息编号 ....参数 (消息体)
        objc_msgSend(s, sel_registerName("sayCode"));
        
        // 类方法编译底层
//        id cls = [LGStudent class];
//        void *pointA = &cls;
//        [(__bridge id)pointA sayNB];
        objc_msgSend(objc_getClass("LGStudent"), sel_registerName("sayNB"));

        // 向父类发消息(对象方法)
        struct objc_super lgSuper;
        lgSuper.receiver = s;
        lgSuper.super_class = [LGPerson class];
        objc_msgSendSuper(&lgSuper, @selector(sayHello));

        //向父类发消息(类方法)
        struct objc_super myClassSuper;
        myClassSuper.receiver = [s class];
        myClassSuper.super_class = class_getSuperclass(object_getClass([s class]));// 元类
        objc_msgSendSuper(&myClassSuper, sel_registerName("sayNB"));

上边这个向父类发消息(类方法)

调用super_class 父类,然后去元类查找的原因是因为

类方法要到元类去找,元类找不到向元类的父类去找。

s -> s的元类 –> s父类的元类

直接取父类去发送消息的话就是直接

s的父类->s父类的元类

 

 

 

 

 

 

 

 

 

 

发布了83 篇原创文章 · 获赞 12 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/shengdaVolleyball/article/details/104051671