博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
小码哥iOS学习笔记第十三天:消息发送
阅读量:6946 次
发布时间:2019-06-27

本文共 2721 字,大约阅读时间需要 9 分钟。

一、objc_msgSend

  • 创建命令行项目, 定义Person类, 并添加personTest方法

  • main函数中使用Person

  • 使用终端, 执行下面的命令, 生成main.m在底层的main.cpp文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m复制代码
  • 打开main.cpp, 可以看到底层代码如下

  • 其中[person personTest][Person initialize]转化为了下面的代码
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("personTest"));((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("initialize"));复制代码
  • 去掉类型转换后, 底层代码如下
objc_msgSend(person, sel_registerName("personTest"));objc_msgSend(objc_getClass("Person"), sel_registerName("initialize"));复制代码
  • OC中调用方法, 会使用objc_msgSend函数给消息接收者发送消息
  • 所以OC调用方法的过程也被称为消息机制
  • 上面两个方法调用的消息接收者和消息名称如下
objc_msgSend(person, sel_registerName("personTest"));消息接收者: person消息名称: personTestobjc_msgSend(objc_getClass("Person"), sel_registerName("initialize"));消息接收者: objc_getClass("Person") == [Person class]消息名称: initialize复制代码

二、objc_msgSend底层源码

  • 可以在官方提供的源码中找到objc_msgSend的底层实现, 可以看到objc_msgSend底层是由汇编实现的

1、当消息接收者为空时, 直接返回, 结束objc_msgSend函数的调用

  • 通过源码可以看到, objc_msgSend执行一开始, 会先判断消息接收者是否为空, 如果为空直接返回

  • 可以写出模拟代码如下
void objc_msgSend(id receiver, SEL selector) {    // 当 消息接收者 为空时, 直接返回, 结束函数调用    if (receiver == nil) return;}复制代码

2、当消息接收者有值时, 查看缓存

  • 当消息接收者有值时, 会调用CacheLookup

  • CacheLookup, 首先会查找cache缓存, 查询是否已经缓存了方法, 如果已经缓存会直接调用

3、如果方法没有被缓存过, 就会查询方法列表

  • 当方法还没有缓存, 会从方法列表中查找, 此时调用CheckMiss, 传入NORMAL

  • 找到CheckMissNORMAL部分, 调用__objc_msgSend_uncached

  • 接着调用MethodTableLookup

  • 接着可以看到调用了__class_lookupMethodAndLoadCache3函数

  • 搜索__class_lookupMethodAndLoadCache3函数, 可以发现并没有__class_lookupMethodAndLoadCache3的实现部分

  • 这主要是因为汇编中的__class_lookupMethodAndLoadCache3函数, 在C中的函数名需要去掉一个下划线_, 所以函数名应该是_class_lookupMethodAndLoadCache3
  • 查找_class_lookupMethodAndLoadCache3, 可以发现_class_lookupMethodAndLoadCache3函数的实现部分

  • 继续进入lookUpImpOrForward函数, 可以看到当传入cacheYES时, 会查找缓存中的方法
  • 此时传入cache的是NO, 所以不会在调用cache_getImp函数从缓存中查找方法

  • 然而我们还能看到下面还有从缓存中查找方法的代码, 如果找到直接跳转返回

  • 这主要是因为在下图过程中, 开发者有可能会动态添加一些方法到缓存中, 所以才会再次查找一次

  • 当缓存中没有找到需要调用的方法时, 就会在方法列表中查找, 如果找到就会存到缓存cache

  • 已经知道, 方法存在cls->rw->methods中, 而methods是个二位数组, 所以需要进行遍历查询, 这里先拿到一维数组调用search_method_list函数查询

  • 在一维数组中查找方法, 这里有两种情况
    • 第一种: 方法列表已经排好序, 会通过findMethodInSortedMethodList函数查找
    • 第二种: 方法列表没有排好序, 会一个一个遍历查找

  • findMethodInSortedMethodList函数使用的是二分查找的方式查询方法

  • 当找到方法后, 会先将方法存储到cache中, 调用log_and_fill_cache函数进行存储

  • log_and_fill_cache中调用cache_fill函数

  • cache_fill函数中调用cache_fill_nolock函数

  • cache_fill_nolock函数中,可以看到方法存储到了buckets

  • 如果在自己的类对象中没有找到需要调用的方法, 就会去查找父类中是否有该方法
    • 1.查找时会一层一层遍历所有父类, 只要某个父类中找到方法, 就会结束查找
    • 2.先从父类的缓存中找, 如果找到, 会先存到自己的cache
    • 3.如果父类的缓存中没有该方法, 就会从父类的方法列表中查找, 如果找到就会存入到自己的cache中, 并不会存入到父类的cache
    • 4.如果没找到, 就会通过for循环查看父类的父类中有没有方法, 依次类推, 只要找到就会结束查询, 并存到自己的cache

  • 如果最后还是没找到, 就会进入下一个阶段, 动态解析阶段

4、消息机制流程图

转载于:https://juejin.im/post/5c872afcf265da2dce1f7c95

你可能感兴趣的文章
我的友情链接
查看>>
vsftp 配置参数说明
查看>>
phpcms2008index.html当前位置导航phpcms全局变量搜索模块
查看>>
TangYuan使用教程-组合SQL服务标签
查看>>
Mac OS X下安装pyenv
查看>>
CentOS6.x 下 LNMP环境搭建(一、安装 MySQL)
查看>>
DNS中的递归查询与迭代查询
查看>>
NFS详解
查看>>
下载中心常见问题解答【Q&A帮助】
查看>>
教你怎么追一个女孩子,哥认为很有道理。
查看>>
索骥馆-网络营销之《锦囊妙计 网站推广101招 第7版》扫描版[PDF]
查看>>
我的友情链接
查看>>
Swift编程高级教程 变量与常量
查看>>
CCBPM 任务分配流程设计说明
查看>>
css 导航栏选中背景高亮显示
查看>>
[android]-记录日志到sd卡
查看>>
左图有文本,图片自由缩放
查看>>
错误:ERROR 1221 (HY000): Incorrect usage of DB GR...
查看>>
服务器性能测试之iperf,iozone,sysbench
查看>>
Linux终端查看
查看>>