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

相关文章

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Nginx分布式部署流程分析

《Nginx分布式部署流程分析》文章介绍Nginx在分布式部署中的反向代理和负载均衡作用,用于分发请求、减轻服务器压力及解决session共享问题,涵盖配置方法、策略及Java项目应用,并提及分布式事... 目录分布式部署NginxJava中的代理代理分为正向代理和反向代理正向代理反向代理Nginx应用场景

Redis中的有序集合zset从使用到原理分析

《Redis中的有序集合zset从使用到原理分析》Redis有序集合(zset)是字符串与分值的有序映射,通过跳跃表和哈希表结合实现高效有序性管理,适用于排行榜、延迟队列等场景,其时间复杂度低,内存占... 目录开篇:排行榜背后的秘密一、zset的基本使用1.1 常用命令1.2 Java客户端示例二、zse

Redis中的AOF原理及分析

《Redis中的AOF原理及分析》Redis的AOF通过记录所有写操作命令实现持久化,支持always/everysec/no三种同步策略,重写机制优化文件体积,与RDB结合可平衡数据安全与恢复效率... 目录开篇:从日记本到AOF一、AOF的基本执行流程1. 命令执行与记录2. AOF重写机制二、AOF的

Java发送SNMP至交换机获取交换机状态实现方式

《Java发送SNMP至交换机获取交换机状态实现方式》文章介绍使用SNMP4J库(2.7.0)通过RCF1213-MIB协议获取交换机单/多路状态,需开启SNMP支持,重点对比SNMPv1、v2c、v... 目录交换机协议SNMP库获取交换机单路状态获取交换机多路状态总结交换机协议这里使用的交换机协议为常

MyBatis Plus大数据量查询慢原因分析及解决

《MyBatisPlus大数据量查询慢原因分析及解决》大数据量查询慢常因全表扫描、分页不当、索引缺失、内存占用高及ORM开销,优化措施包括分页查询、流式读取、SQL优化、批处理、多数据源、结果集二次... 目录大数据量查询慢的常见原因优化方案高级方案配置调优监控与诊断总结大数据量查询慢的常见原因MyBAT

分析 Java Stream 的 peek使用实践与副作用处理方案

《分析JavaStream的peek使用实践与副作用处理方案》StreamAPI的peek操作是中间操作,用于观察元素但不终止流,其副作用风险包括线程安全、顺序混乱及性能问题,合理使用场景有限... 目录一、peek 操作的本质:有状态的中间操作二、副作用的定义与风险场景1. 并行流下的线程安全问题2. 顺

MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决

《MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决》MyBatis默认开启一级缓存,同一事务中循环调用查询方法时会重复使用缓存数据,导致获取的序列主键值均为1,... 目录问题原因解决办法如果是存储过程总结问题myBATis有如下代码获取序列作为主键IdMappe

Java中最全最基础的IO流概述和简介案例分析

《Java中最全最基础的IO流概述和简介案例分析》JavaIO流用于程序与外部设备的数据交互,分为字节流(InputStream/OutputStream)和字符流(Reader/Writer),处理... 目录IO流简介IO是什么应用场景IO流的分类流的超类类型字节文件流应用简介核心API文件输出流应用文

基于Python实现自动化邮件发送系统的完整指南

《基于Python实现自动化邮件发送系统的完整指南》在现代软件开发和自动化流程中,邮件通知是一个常见且实用的功能,无论是用于发送报告、告警信息还是用户提醒,通过Python实现自动化的邮件发送功能都能... 目录一、前言:二、项目概述三、配置文件 `.env` 解析四、代码结构解析1. 导入模块2. 加载环