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

2023-11-29 06:48

本文主要是介绍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: LGPerson0x100000b52 <+34>:  movq   0x71f(%rip), %rcx         ; "alloc"0x100000b59 <+41>:  movq   %rsi, %rdi0x100000b5c <+44>:  movq   %rcx, %rsi0x100000b5f <+47>:  movq   %rax, -0x48(%rbp)0x100000b63 <+51>:  callq  *0x4a7(%rip)              ; (void *)0x0000000100344640: objc_msgSend0x100000b69 <+57>:  movq   0x710(%rip), %rsi         ; "init"0x100000b70 <+64>:  movq   %rax, %rdi0x100000b73 <+67>:  callq  *0x497(%rip)              ; (void *)0x0000000100344640: objc_msgSend0x100000b79 <+73>:  movq   %rax, -0x18(%rbp)0x100000b7d <+77>:  movq   0x714(%rip), %rax         ; (void *)0x00000001000012d8: LGPerson0x100000b84 <+84>:  movq   0x6fd(%rip), %rsi         ; "class"0x100000b8b <+91>:  movq   %rax, %rdi0x100000b8e <+94>:  callq  *0x47c(%rip)              ; (void *)0x0000000100344640: objc_msgSend0x100000b94 <+100>: movq   %rax, -0x20(%rbp)0x100000b98 <+104>: movq   -0x18(%rbp), %rax0x100000b9c <+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_msgSendUNWIND _objc_msgSend, NoFramecmp	p0, #0			// nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERSb.le	LNilOrTagged		//  (MSB tagged pointer looks negative)
#elseb.eq	LReturnZero
#endif// person - isa - 类ldr	p13, [x0]		// p13 = isaGetClassFromIsa_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 == GETIMPcbz p9, LGetImpMiss
.elseif $0 == NORMALcbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUPcbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro

CheckMiss

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

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

    STATIC_ENTRY __objc_msgSend_uncachedUNWIND __objc_msgSend_uncached, FrameWithNoSaves// THIS IS NOT A CALLABLE C FUNCTION// Out-of-band p16 is the class to searchMethodTableLookupTailCallFunctionPointer x17END_ENTRY __objc_msgSend_uncached
  • 类的结构。先通过isa找到类,再找到类的 cache 的 buckets 中 ,当缓存之中全都没有找到之后,那么接下来就去类原来的存储空间,也就是  class - bits - rw - ro - methodList  中去找方法的定义。
  • 消息发送的汇编也是一样的在 MethodTableLookup 方法中,先是做了一系列准备工作,之后调用了__class_lookupMethodAndLoadCache3 方法,进行方法查找和加载缓存,
  • 下面汇编结束是 C/C++
macro MethodTableLookup// push frameSignLRstp fp, lr, [sp, #-16]!mov fp, sp// 一系列准备工作// save parameter registers: x0..x8, q0..q7sub 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 x1mov 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父类的元类

 

 

 

 

 

 

 

 

 

 

这篇关于6、iOS底层分析 - 消息发送(objc_msgeSend)分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/431851

相关文章

Springboot中分析SQL性能的两种方式详解

《Springboot中分析SQL性能的两种方式详解》文章介绍了SQL性能分析的两种方式:MyBatis-Plus性能分析插件和p6spy框架,MyBatis-Plus插件配置简单,适用于开发和测试环... 目录SQL性能分析的两种方式:功能介绍实现方式:实现步骤:SQL性能分析的两种方式:功能介绍记录

如何通过Python实现一个消息队列

《如何通过Python实现一个消息队列》这篇文章主要为大家详细介绍了如何通过Python实现一个简单的消息队列,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录如何通过 python 实现消息队列如何把 http 请求放在队列中执行1. 使用 queue.Queue 和 reque

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

MySQL中的MVCC底层原理解读

《MySQL中的MVCC底层原理解读》本文详细介绍了MySQL中的多版本并发控制(MVCC)机制,包括版本链、ReadView以及在不同事务隔离级别下MVCC的工作原理,通过一个具体的示例演示了在可重... 目录简介ReadView版本链演示过程总结简介MVCC(Multi-Version Concurr

C#使用DeepSeek API实现自然语言处理,文本分类和情感分析

《C#使用DeepSeekAPI实现自然语言处理,文本分类和情感分析》在C#中使用DeepSeekAPI可以实现多种功能,例如自然语言处理、文本分类、情感分析等,本文主要为大家介绍了具体实现步骤,... 目录准备工作文本生成文本分类问答系统代码生成翻译功能文本摘要文本校对图像描述生成总结在C#中使用Deep

解读Redis秒杀优化方案(阻塞队列+基于Stream流的消息队列)

《解读Redis秒杀优化方案(阻塞队列+基于Stream流的消息队列)》该文章介绍了使用Redis的阻塞队列和Stream流的消息队列来优化秒杀系统的方案,通过将秒杀流程拆分为两条流水线,使用Redi... 目录Redis秒杀优化方案(阻塞队列+Stream流的消息队列)什么是消息队列?消费者组的工作方式每

使用C/C++调用libcurl调试消息的方式

《使用C/C++调用libcurl调试消息的方式》在使用C/C++调用libcurl进行HTTP请求时,有时我们需要查看请求的/应答消息的内容(包括请求头和请求体)以方便调试,libcurl提供了多种... 目录1. libcurl 调试工具简介2. 输出请求消息使用 CURLOPT_VERBOSE使用 C

Java中Springboot集成Kafka实现消息发送和接收功能

《Java中Springboot集成Kafka实现消息发送和接收功能》Kafka是一个高吞吐量的分布式发布-订阅消息系统,主要用于处理大规模数据流,它由生产者、消费者、主题、分区和代理等组件构成,Ka... 目录一、Kafka 简介二、Kafka 功能三、POM依赖四、配置文件五、生产者六、消费者一、Kaf

Python手搓邮件发送客户端

《Python手搓邮件发送客户端》这篇文章主要为大家详细介绍了如何使用Python手搓邮件发送客户端,支持发送邮件,附件,定时发送以及个性化邮件正文,感兴趣的可以了解下... 目录1. 简介2.主要功能2.1.邮件发送功能2.2.个性签名功能2.3.定时发送功能2. 4.附件管理2.5.配置加载功能2.6.

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制