函数的调用机制
 
函数的调用机制是在函数调用时通过那种路径走到最终调用函数地址的机制。
在编程语言中,函数的调用机制有三种
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. mysql查询表中一个字段第三个字母为A第五个字母为R的sql

    select id ,name, age from user where name like ' _ _A_R%';

  2. chatgpt接口开发笔记1:completions接口

    chatgpt接口开发笔记1:completions接口 个人博客地址: https://note.raokun.top 拥抱ChatGPT,国内访问网站:https://www.playchat.t ...

  3. Claude:除ChatGPT外的另一种选择

    前言 Claude 是 Anthropic 开发的人工智能产品.Anthropic 是由 11 名前 OpenAI 员工于 2022 年创立的人工智能公司,旨在构建安全.可解释和有益于人类的人工智能系 ...

  4. day10-好友关注

    功能05-好友关注 6.功能05-好友关注 6.1关注和取关 6.1.1需求分析 在探店图文的详情页面中,可以关注发布笔记的作者: 关注和取关:点击关注按钮就会发出请求(上图):http://127. ...

  5. 【python爬虫】bilibili每周必看页面视频图片爬取

    此博客仅作为交流学习 对于使用bilibili上学习和娱乐的小伙伴们有时会看到视频博主发布的视频封面好看想要得到,但是苦于没有方法,这次我用python来爬取bilibili每周必看页面视频图片. 首 ...

  6. 2023-02-12:给定正数N,表示用户数量,用户编号从0~N-1, 给定正数M,表示实验数量,实验编号从0~M-1, 给定长度为N的二维数组A, A[i] = { a, b, c }表示,用户i报

    2023-02-12:给定正数N,表示用户数量,用户编号从0~N-1, 给定正数M,表示实验数量,实验编号从0~M-1, 给定长度为N的二维数组A, A[i] = { a, b, c }表示,用户i报 ...

  7. 2020-11-16:手写代码:leetcode第406题。假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。

    福哥答案2020-11-16: ①排序.按照[身高]降序排列.如果[身高]一样,按照[人数]升序排列.②插入.遍历这个队列,按照[人数]插入相应位置. 采用leetcode里的代码,golang代码如 ...

  8. 2022-07-19:f(i) : i的所有因子,每个因子都平方之后,累加起来。 比如f(10) = 1平方 + 2平方 + 5平方 + 10平方 = 1 + 4 + 25 + 100 = 130。

    2022-07-19:f(i) : i的所有因子,每个因子都平方之后,累加起来. 比如f(10) = 1平方 + 2平方 + 5平方 + 10平方 = 1 + 4 + 25 + 100 = 130. ...

  9. 2021-03-26:给定一个正整数N,表示有N份青草统一堆放在仓库里。有一只牛和一只羊,牛先吃,羊后吃,它俩轮流吃草。不管是牛还是羊,每一轮能吃的草量必须是:1,4,16,64…(4的某次方)。谁最先把草吃完,谁获胜。假设牛和羊都绝顶聪明,都想赢,都会做出理性的决定。根据唯一的参数N,返回谁会赢。

    2021-03-26:给定一个正整数N,表示有N份青草统一堆放在仓库里.有一只牛和一只羊,牛先吃,羊后吃,它俩轮流吃草.不管是牛还是羊,每一轮能吃的草量必须是:1,4,16,64-(4的某次方).谁最 ...

  10. PictureBox 从数据库加载图片照片

    Private Sub PAPHOTO_SEL() Try Dim objCon As SqlConnection Dim objCmd As SqlCommand '打开数据库 objCon = N ...