函数的调用机制
 
函数的调用机制是在函数调用时通过那种路径走到最终调用函数地址的机制。
在编程语言中,函数的调用机制有三种
1.静态调用:编译期就确定了函数内存地址,执行效率最高,还可以使用编译器优化如:inline函数内联提升执行效率。缺点:因为函数调用的内存地址在编译期已经确定,则无法支持继承等动态修改调用的方式。
2.函数表调用:每个类都有一份自己的v-table虚函数表,里面是以函数名为key, 函数地址为value; 如果子类override了父类的方法,那么这个方法名key对应的value就是那个子类重写的新的函数地址。
3.消息发送调用:所有的函数调用最后都会转换成一系列参数,通过消息发送的方式进行调用。这种方式最灵活,可以override重写父类方法,可以swizzle交互类中方法的实现,可以swizzle_isa修改类的父类即:修改继承链。
 
常见语言的调用机制
C++:默认使用静态调用机制,可以通过virtural修饰成使用函数表机制调用。
Java:默认使用函数表调用机制,可以通过final修饰成直接调用机制。
Object-C:只能使用消息发送的方式进行方法调用,可以使用C代码写直接调用机制的代码。
Swift:可以根据不同的修饰符,根据情况使用上面三种方式的任一方式。
Swift中的函数调用方式
Swift的调用方法非常灵活,它三种类型都支持。
首先在大的分类上分2种:Static Dispatch 和 Dynamic Dispatch。
Static Dispatch是静态调用,调用方法的函数地址是在编译时确定的。
Dynamic Dispatch是动态调用,调用的函数地址要在运行时才能确定。
Dynamic Dispatch动态调用又可以分为3个子类:V-Table Dispatch, witness table dispatch, objc_msgSend。
Swift的类型分2种,值类型和引用类型。值类型包含结构体和枚举,对于值类型中的方法调用基本都是静态调用,执行效率非常高。
引用类型就是对象,Swift中的类型分为是否继承自NSObject, 原因是如果继承NSObject,那么对象的结构体中(类的底层实现也是一个结构体)就有了OC运行时中那一套的所有机制,isa指针,方法列表,属性列表,协议列表等。从而函数调用就支持消息发送方式了。这也是Swift类中方法走消息发送的前提条件。
值类型-Struct
1.因为结构体不能继承,所以它的struct下定义的方法的调用都是静态的。
2.它的extense下扩展的方法不能被override,走的也是静态的。
3.调用遵守协议的方法实现就是自己定义的方法一样。
引用类型-纯Swift类
1.在Swift类中定义的方法,影响它调用方式的只有final关键字,正常定义的方法Swift通过一种Virtual Table的机制在运行时寻找方法的内存地址并调用。被final修饰的方法不能被override,走静态调用。
2.它的extense下扩展的方法不能被override,走静态调用。
3.调用遵守协议的方法实现就是自己定义的方法一样,走V-Table虚表调用。
引用类型-Swift类继承自NSObject
这种方式创建的Swift类,调用方式受关键字影响比较大。
1.被final修饰的,走静态调用,因为不能被override。
2.类中定义的普通方法和被@objc修饰的方法,都是走的V-Table方式调用。NSObject的子类+@objc修饰符的方法,只能表示可以让OC类进行调用,但真正的执行机制还是走的V-Table虚表调用。
3.@objc+dynamic修饰的方法走OC的runtime消息发送。
4.Extense下的方法默认走静态调用,因为不能被override。如果此时被@objc和dynamic修饰,就无法走静态,走的OC的runtime消息发送。
5.调用遵守协议的方法实现就是自己定义的方法一样。
上面是在没有做编译器优化的情况下,如果做了编译器优化,则编译器会尽可能走静态调用的方式,提高运行效率。
Protocal
1.当变量以当前对象的方式调用时,走的是当前对象定义方法的方式。
2.当变量以Protocal协议对象的方式调用时,走的是Witness Table
3.协议中被@objc修饰的方法,走的是runtime的消息发送

判断函数是以哪种方式调用的方法
静态调用
因为静态调用时,call函数的地址是固定的,根据machO文件加载到内存的内存分布

所以,静态调用时,函数地址是一个固定的,比栈变量内存地址小的内存地址。
消息发送
消息发送时,函数调用通常都会走到同一段函数内存地址中,因为所有的函数调用都是使用同一个消息发送方法进行的。
函数表调用
函数调用的实际内存地址通常需要根据偏移量动态计算而来, 不像静态调用和消息发送在汇编代码里有明显的特征。

可以通过汇编调试进行佐证上面的函数调用方式
查看寄存器中的值
register read/格式
register read/x register write 寄存器名称 数值
register write rax 0

查看具体内存地址中的值

x/数值-格式-字节大小 内存地址
x/3xw 0x000010 memory write 内存地址 数值
memory write 0x000010 10
Swift和OC在模拟器调试时,采用的汇编类型是AT&T
常用寄存器说明:
rax寄存器常用于函数传参和返回值
rbp,rsp寄存器常用于栈数据的读取
rip常用于指令寄存器
常用指令说明:
movq $30 %rax  //将30数据放置在rax寄存器中
leaq -0x86(%rbp) %rax //将%rbp-0x86中的内存地址放置在rax寄存器中
call -0x86(%rbp) //函数调用,下面通常有ret命令对应
jump -0x86(%rbp) //if跳转
常见表示说明:
0x4bdc(%rip),一般是全局变量,全局区(数据段)
-0x78(%rbp), 一般是局部变量,栈空间
0x10(%rax), 一般是堆空间
参考文章:
https://zhuanlan.zhihu.com/p/35696161
https://www.cnblogs.com/zhou--fei/p/17245908.html
 
 

Swift函数调用方式浅析的更多相关文章

  1. (转)函数调用方式与extern "C"

    原文:http://patmusing.blog.163.com/blog/static/13583496020103233446784/ (VC编译器下) 1. CALLBACK,WINAPI和AF ...

  2. 函数调用方式__stdcall、__cdel

    函数调用方式关系对比如下: 关键字 调用规则 参数传递方向 返回 参数寄存器 堆栈的清除 __cdecl C语言 从右向左 EAX 无 调用者 __stdcall Win32标准  从右向左 EAX ...

  3. JavaScript中七种函数调用方式及对应 this 的含义

    this 在 JavaScript 开发中占有相当重要的地位,不过很多人对this这个东西都感觉到琢磨不透.要真正理解JavaScript的函数机制,就非常有必要搞清楚this到底是怎么回事. 函数调 ...

  4. 前端JS面试题汇总 Part 3 (宿主对象与原生对象/函数调用方式/call与apply/bind/document.write)

    原文:https://github.com/yangshun/front-end-interview-handbook/blob/master/questions/javascript-questio ...

  5. jQuery内部原理和实现方式浅析

    这篇文章主要介绍了jQuery内部原理和实现方式浅析,本文试图从整体来阐述一下jQuery的内部实现,需要的朋友可以参考下 这段时间在学习研究jQuery源码,受益于jQuery日益发展强大,研究jQ ...

  6. 函数调用方式--__thiscall调用方式和__cdecl,__stdcall有什么区别

    函数调用方式--__thiscall调用方式和__cdecl,__stdcall有什么区别 首先,__thiscall是关于类的一种调用方式,它与其他调用方式的最大区别是:    __thiscall ...

  7. 【Python】利用递归函数调用方式,将所输入的字符串,以相反的顺序显示出来

    源代码: """ 利用递归函数调用方式,将所输入的字符串,以相反的顺序显示出来 string_reverse_output():反向输出字符串的自定义函数 pending ...

  8. JavaScript系列:函数调用方式

    有关JS的问题,持续更新.. 一,函数调用的4种方式 1,函数调用模式 //下面这种模式叫 “函数调用模式”:窗后window来调用 //函数调用四种方式的基础 //这tm不就是作用域this的问题吗 ...

  9. C++对象模型的那些事儿之六:成员函数调用方式

    前言 C++的成员函数分为静态函数.非静态函数和虚函数三种,在本系列文章中,多处提到static和non-static不影响对象占用的内存,而虚函数需要引入虚指针,所以需要调整对象的内存布局.既然已经 ...

  10. Swift 函数调用到底写不写参数名

    最近真正开始学 Swift,在调用函数的时候遇到一个问题:到底写不写函数名? 我们来看两个个例子: // 1 func test(a: Int, b: Int) ->Int { return a ...

随机推荐

  1. Java关键字以及标识符

    Java中有许多关键字,关键字是什么意思呢? 我用自己的分析来表达一下吧. Java就是源自于生活的,我们都有自己的名字.所以它也会有许多的名字,每个名字都有各自不同的特性(作用),都是系统定义好的. ...

  2. 微信小程序隐藏页面滚动条

    开发小程序时,经常会碰到页面长度超过屏幕高度,然后下拉时会出现滚动条,对于一些有强迫症的人来说是不可忍受的. 网上看了好多,写的.都评论有起作用或者不起作用的. 我在这分享一个全局隐藏滚动条的方式. ...

  3. Node工程的依赖包管理方式

    作者:京东零售 陈震 在前端工程化中,JavaScript 依赖包管理是非常重要的一环.依赖包通常是项目所依赖的第三方库.工具和框架等资源,它们能够帮助我们减少重复开发.提高效率并且确保项目可以正确的 ...

  4. vue2路由导航守卫(钩子函数)

    https://router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E5%85%A8%E5%B1%80%E5%89%8D%E7%BD% ...

  5. 【C#】图片上传并根据长宽大小进行正方形、长方形及等比缩放。

    #region 正方型裁剪并缩放 /// <summary> /// 正方型裁剪 /// 以图片中心为轴心,截取正方型,然后等比缩放 /// 用于头像处理 /// </summary ...

  6. 2021-05-08:给定两个非负数组x和hp,长度都是N,再给定一个正数range。x有序,x[i]表示i号怪兽在x轴上的位置;hp[i]表示i号怪兽的血量 。range表示法师如果站在x位置,用A

    2021-05-08:给定两个非负数组x和hp,长度都是N,再给定一个正数range.x有序,x[i]表示i号怪兽在x轴上的位置:hp[i]表示i号怪兽的血量 .range表示法师如果站在x位置,用A ...

  7. 2021-07-12:缺失的第一个正数。给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。比如[3,4,5

    2021-07-12:缺失的第一个正数.给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数.请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案.比如[3,4,5 ...

  8. openstack部署2

    检查服务,查看dashboard页面有哪些功能 检查服务状态 检查计算节点,控制节点服务是up状态 检查网络节点是True的状态.这里的每个计算节点,都是一个neutron的客户端. 查看dashbo ...

  9. Vulnhub-Durian

    Durian 一.靶机信息 # 靶机地址 http://www.vulnhub.com/entry/durian-1,553/ s #参考 https://blog.csdn.net/LYJ20010 ...

  10. 洛谷 P8742题解

    简单版(P2347)传送门 原题传送门 有一道类似的题目(P2347),先扯一扯~ 1.P2347 题目分析 动态规划入门题(01背包可行性问题)~ 我们设 \(dp_j\) 为能否用砝码称出 \(j ...