前言:
本篇仅为视频学习笔记
内联函数 (Inline Function)
内联函数在C++这个函数里是有的,那么在swift里面,怎么做的呢?swift内是不需要我们去声明这个函数为内联函数的。
★ 如果开启了编译器优化(Realease 模式默认会开启优化),编译器会自动将某些函数变成内联函数。
什么意思呢?我们先来理解前半段如果开启了编译器优化(Realease 模式默认会开启优化)。
我们还是打开之前的命令行项目。
选择target---> Build Settings ---> 输入optimization 如下图:
搜索一下,我们会看到有一个Optimization Level 优化级别,默认Debug情况下是NO Optimization(没有优化)。Release(打包的时候)是Optimization for Speed[-D]是有优化的。而且是speed是最快的,按照速度最快的方式去优化。如果我们开启了优化的话,它会自动将我们的某些函数变成内联函数。也就是说,Debug模式下,不会将你的函数,变成内联函数。Release就变成内联函数。Release发布版会自动将某些函数变成内联函数,也就意味这内联函数这种东西是有用的。肯定是可以优化我们程序的系统的。
那么内联函数有什么用呢?
★ 将函数调用展开函数体
举个例子
打开我们的工程TestSwift。
如上图,像这个是什么意思呢?按照我们正常的理解,肯定会执行第14行代码,一旦执行第14行代码就会调用test()这个函数,并且开辟栈空间,给这个函数,在这个函数栈空间里面,去做它相应的事情。比如说分配局部变量,做相应的操作。
等这个函数执行完之后呢?就会将它的栈空间回收,所以这里牵扯一个栈空间的开辟跟回收的一个问题。所以,一旦调用函数就会出这个问题。
思考一下,不觉得这段代码能够优化成这样 print("test")性能更好吗?如下图:
因为你这个函数里面的代码,特别的少。就是做一件什么事情,打印。还不如把函数代码抽出来,让它直接打印呢?如下图:
那么,这样不是性能更高吗?内联函数就是这个意思。内联函数会自动将函数调用展开成函数体代码。说白了,是一个怎么样的函数呢?如果你这个test是一个内联函数的话,它会之间将你的函数调用,展开成函数体print("test")。这样就是一种优化,这样可以减少函数的调用开销,就不用开辟栈空间,撤销栈空间。
Debug是没有优化的,Release是优化的,用汇编看一下到底有没有优化
在test()带一个断点,cmd + R 运行
我们之前,说过Swift是不用编写main函数的,它会把我们第一句可执行代码,当作我们程序的入口,其实并不是没有main函数,main函数编译器会帮我们搞。
你会发现,TestSwift`main是有main函数的,不过它会自己帮我们写,不用我们写而已。
我们现在看这个test函数调用转成了汇编,如下图,我们看到了:
说白了,这个函数并没有进行内联操作。那么,我们现在试着做一个什么事情呢?Debug模式改成改成优化模式,如下图:
再打个断点,cmd + R,运行一下
注意看一个问题,test已经打印出来了,改一下,在cmd + R运行一下
你会发现,那个断点进不去啊,所白了它压根没有执行test()这个代码。但是这个代码print("test123")它肯定会执行,所以这个时候,我们把断点打到print("test123")这个位置。cmd + R运行一下。
就会跳转到了汇编页面,如下图:
我们认真观察一下,首先来到最上边TestSwift`main:。main函数里面就有print函数如下图:
print上面到断点这部分就是为打印作准备的,因为你要传参吗各种。然后接下来调用print,也就说白了print("test123")这句代码放到了main函数里
所以,看的出来,我们一旦开启了编译器的优化,它确实会将我们的函数进行内联,直接将它函数体代码,放到这个位置test()。
但是注意,说的是自动将某些函数进行内联,那就是说有一些函数不会进行内联,那么,那些函数不会被内联呢?
那些函数不会被内联呢?
★ 函数体比较长
就是如果函数内部,写了很多的时候,它发现代码比较长,它就不会进行内联,它就不会将你的函数体代码放到调用的位置,如下图:你知道为什么吗?那么,你思考一下,如果这个函数调用次数比较多,假设如下图test()调用的比较多,那么你要内联的话,那不就相当于把函数里所有代码,这里放一份,那里放一份。
那么这样导致的结果是是什么呢?那就是生成的汇编特别多,最终的机器也就是01、01特别多,所以就会导致你代码的体积就会变大,到时候你的安装包也就会变大,所以这个也是比较智能的。编译器会自动去识别,它认为合适的就会进行内联,不合适的它不会内联,说白了,上面代码,就算你开启了编译器,编译器的优化,它也会变成函数调用,不会给你做内联优化
★ 包含递归调用
如果你包含了递归调用,也不会内联。什么意思呢?思考一下,如果如下面代码这样写:
像这种,它也不会内联,因为你想想,如果这种要搞内联,那不是搞晕了吗?内联是什么意思,内联就是将函数调用展开成函数体代码,那你函数体就这一句 test()
说白了,你函数外边test(),展开后还是 test(),没完没了,就是一个死循环,所以编译器也是很聪明的,发现你有递归调用也不会给你内联。
★ 包含动态派发
什么叫动态派发呢?其实就是OC里面的动态绑定,如果包含了动态派发的函数,它也不会进行内联。
比如说,我们有两个类,一个Person类和Student类,Student类继承于Person。 Person中有一个test函数方法,子类Student,重写一下父类的test方法。如下图:
认真思考一个问题,举个例子
上边图片,的两句代码,明显是一个多态。相当于OC里面的父类指针指向子类对象。那么你想一下test这个函数这个将来肯定要动态派发的。所谓动态派发就是在运行时再决定调用谁的test。想想是不是。
程序运行过程中,根据你的变量指向的对象来调用谁。再举个例子,如果下面有个Teacher类
所以,你思考一下,到时候如下图,可能会变。
就是说到时候,可能会指向Teacher,既然你这个变量,将来指向的对象是随时可能会发生变化的。所以编译器在编译这个代码的时候,没办法确定到底是调用Teacher类、还是Student类中的test,所以这个叫做动态派发。没有办法进行内联。
想一想,内联的前提是什么?我已经确定要调用某个,比如说我确定在编译时期了你要调用某个类的test,那么就将函数体代码放到这个位置如下图:
但是,像上面这个,明显就是不行。