本文共 2103 字,大约阅读时间需要 7 分钟。
这篇文章将认真彻底地分析 OC对objc_msgSend
的尾调用优化
。
尾调用(
提醒:注意 “仅仅” 两个字。Tail Call
):某个函数的最后一步仅仅只是调用了一个函数(可以是自身,可以是另一个函数)。
// 尾调用:- (NSInteger)funcA:(NSInteger)num { /* Some codes... */ if (num == 0) { return [self funcA:num];// 尾调用->自身 } if (num > 0) { return [self funcB:num];// 尾调用->函数funcB } return [self funcC:num];// 尾调用->函数funcC}
正例解释:funcA的最后一步仅仅调用了另一个函数。不论是调用funcA、funcB还是funcC都属于尾调用。~(不论调用函数的位置在哪,只要最后一步仅仅调用一个函数就行)~
// 不是尾调用1:- (NSInteger)funcA:(NSInteger)num { NSInteger num = [self funcB:(num)]; return num;// 不是尾调用->最后一步是返回一个值,而不是调用一个函数}
反例解释:不是尾调用。因为最后一步是返回一个值,而不是仅仅调用一个函数
// 不是尾调用2:- (NSInteger)funcA:(NSInteger)num { return [self funcB:(num)] + 1;// 不是尾调用->原因:最后一步不仅调用了函数还有 +1 操作}
反例解释:不是尾调用。因为最后一步不仅调用了函数还有 +1 操作
小编准备了一个demo:通过“断点”和“当前内存情况”查看有无尾调用优化
无优化Demo效果图:
**解释:
这种场景下,每次函数调用一直在进栈,不断申请栈空间,最后会栈溢出,最终导致崩溃。空间复杂度O(n),时间复杂度O(n)。**下面请看图解:
优化Demo效果图:
**解释:
这种场景下,每次函数调用一直在重用栈帧,不申请栈空间。空间复杂度O(1),时间复杂度O(n)。**下面请看图解:
这次讨论起因于《Effective Objective-C 2.0》作者的原话:
如果某函数的最后一项操作是调用另外一个函数,那么就可以运用“ 尾调用优化 ”
技术。编译器会生成调转至另一函数所需的指令码,而且不会向调用堆栈中推入新的“栈帧”
(frame stack)。只有当某函数的最后一个操作仅仅是调用其他函数而不会将其返回值另作他用
时,才能执行“ 尾调用优化 ”
。这项优化对objc_msgSend
非常关键,如果不这么做的话,那么每次调用Objective-C方法之前,都需要为调用objc_msgSend函数准备“栈帧”,大家在“栈踪迹”(stack trace)中可以看到这种“栈帧”。此外,如果不优化,还会过早地发生“栈溢出”(stack overflow)现象。
作者这一段概括的话,很精简。而小编第一次看时,感觉很懵懂。在这里,QiShare对这段话进行了详细的分析:
尾调用优化的条件有三点:
4. 尾调用优化实现原理:当函数A的最后一步仅仅是调用另一个函数B时(或者调用自身函数A),这时,因为函数A的位置信息和内部变量已经不会再用到了,直接把函数A的栈帧交给函数B使用。
总结:
1. 尾调用:某个函数的最后一步**仅仅**调用了一个函数(可以是自身,可以是另一个函数)。2. OC的尾调用优化的本质是:[栈帧](https://baike.so.com/doc/9968763-10316382.html)的复用3. 尾调用优化实现**原理**:当函数A的最后一步仅仅是调用另一个函数B时(或者调用自身函数A),这时,因为函数A的位置信息和内部变量已经不会再用到了,直接把函数A的栈帧交给函数B使用。
PS:尾调用优化在Release模式下才会有,Debug模式下没有。
转载地址:http://sudxx.baihongyu.com/