函数的调用机制
 
函数的调用机制是在函数调用时通过那种路径走到最终调用函数地址的机制。
在编程语言中,函数的调用机制有三种
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. 参与开源之夏 x OpenTiny 跨端跨框架 UI 组件库贡献,可以赢取奖金🏆!这份《OpenTiny 开源贡献指南》请收好🎁!

    大家好,我是 Kagol. 近期有几位朋友在 OpenTiny 技术交流群里询问我们在开源之夏(OSPP)的项目,希望能提前做一些准备工作. 这里给大家简单介绍下开源之夏. 开源之夏是由中科院软件所& ...

  2. Docker Go语言程序的编译与打包

    使用Docker打包Go程序的镜像 Golang镜像 首先使用docker pull获取golang镜像 $ sudo docker pull golang:1.18.3 查看镜像: $ sudo d ...

  3. Python tkinter 进度条代码

    1 import tkinter as tk 2 import time 3 4 # 创建主窗口 5 window = tk.Tk() 6 window.title('进度条') 7 window.g ...

  4. 2020-10-28:go中,好几个go程,其中一个go程panic,会产生什么问题?

    福哥答案2020-10-28: 1.运行时恐慌,当panic被抛出异常后,如果我们没有在程序中添加任何保护措施的话,程序就会打印出panic的详细情况之后,终止运行.2.有panic的子协程里的def ...

  5. 2022-07-28:以下go语言代码输出什么?A:AA;B:AB;C:BA;D:BB。 package main import ( “fmt“ ) func main() { f

    2022-07-28:以下go语言代码输出什么?A:AA:B:AB:C:BA:D:BB. package main import ( "fmt" ) func main() { f ...

  6. 2022-05-20:给定一个正数数组arr,长度为N,依次代表N个任务的难度,给定一个正数k, 你只能从0任务开始,依次处理到N-1号任务结束,就是一定要从左往右处理任务, 只不过,难度差距绝对值不

    2022-05-20:给定一个正数数组arr,长度为N,依次代表N个任务的难度,给定一个正数k, 你只能从0任务开始,依次处理到N-1号任务结束,就是一定要从左往右处理任务, 只不过,难度差距绝对值不 ...

  7. 2022-05-02:给定一个数组arr,一个正数num,一个正数k, 可以把arr中的某些数字拿出来组成一组,要求该组中的最大值减去最小值<=num, 且该组数字的个数一定要正好等于k, 每个数字只

    2022-05-02:给定一个数组arr,一个正数num,一个正数k, 可以把arr中的某些数字拿出来组成一组,要求该组中的最大值减去最小值<=num, 且该组数字的个数一定要正好等于k, 每个 ...

  8. 2021-05-07:给定一个数组arr,你可以在每个数字之前决定+或者-,但是必须所有数字都参与 ,再给定一个数target,请问最后算出target的方法数是多少?

    2021-05-07:给定一个数组arr,你可以在每个数字之前决定+或者-,但是必须所有数字都参与 ,再给定一个数target,请问最后算出target的方法数是多少? 福大大 答案2021-05-0 ...

  9. Selenium - 元素操作(1) - 基础操作/元素信息/元素检查

    Selenium - 元素操作 元素示例 基础操作 点击元素: .click() # 点击百度一下按钮 driver.find_element_by_id("su").click( ...

  10. 快速上手kettle(三)壶中可以放些啥?

    目录 序言 一 .kettle这壶能装些啥 二.Access输入 2.1 准备Acess数据库和表 2.2 新建一个转换并设置 2.3 启动转换预览数据 三.CSV文件输入 3.1 准备csv文件,并 ...