起因
项目中用到了YYKit中的一些组件,比如YYText和YYImage,于是抽了点时间阅读了一下当中的一些代码。
发现了在YYKit中用了很多的函数方法,比如:
static inline UIEdgeInsets UIEdgeInsetRotateVertical(UIEdgeInsets insets) {
UIEdgeInsets one;
one.top = insets.left;
one.left = insets.bottom;
one.bottom = insets.right;
one.right = insets.top;
return one;
}
这种:
static void YYTextGetRunsMaxMetric(CFArrayRef runs, CGFloat *xHeight, CGFloat *underlinePosition, CGFloat *lineThickness) {
CGFloat maxXHeight = 0;
CGFloat maxUnderlinePos = 0;
CGFloat maxLineThickness = 0;
for (NSUInteger i = 0, max = CFArrayGetCount(runs); i < max; i++) {
CTRunRef run = CFArrayGetValueAtIndex(runs, i);
CFDictionaryRef attrs = CTRunGetAttributes(run);
if (attrs) {
CTFontRef font = CFDictionaryGetValue(attrs, kCTFontAttributeName);
if (font) {
CGFloat xHeight = CTFontGetXHeight(font);
if (xHeight > maxXHeight) maxXHeight = xHeight;
CGFloat underlinePos = CTFontGetUnderlinePosition(font);
if (underlinePos < maxUnderlinePos) maxUnderlinePos = underlinePos;
CGFloat lineThickness = CTFontGetUnderlineThickness(font);
if (lineThickness > maxLineThickness) maxLineThickness = lineThickness;
}
}
}
if (xHeight) *xHeight = maxXHeight;
if (underlinePosition) *underlinePosition = maxUnderlinePos;
if (lineThickness) *lineThickness = maxLineThickness;
}
还有这种:
CGFloat YYTextScreenScale() {
static CGFloat scale;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
scale = [UIScreen mainScreen].scale;
});
return scale;
}
以上三种函数使用相似、长相类似,但是仍然有些许的不同。
我们观察一下,可知:
1、第一个方法,使用了static和inline标示符,返回值显示为UIEdgeInsets类型。
2、第二个方法,只是只用了static,返回值为void类型。
3、第三个方法,干脆连static也省掉了,返回值为CGFloat。
那么三者到底有什么区别呢?往下看
分析
上面观察得出,三者基本上的区别就是标示符使用上的区别,那么我们分析下,表示符不同使用情况下,会有什么优势和好处。
一、引用inline
标示符
引用inline
标示符,能够使函数一作为一个标准的内联函数,函数的代码被放入符号表中,在使用时直接进行替换,(像宏一样展开)。
一般情况下引入内联函数是为了解决函数调用效率
的问题,函数之间调用,是内存地址之间的调用,当函数调用完毕之后还会返回原来函数执行的地址,会有一定的时间开销,内联函数就是为了解决这一问题。
不用inline
修饰的函数,汇编时会调用 call 指令,调用call指令就是就需要:
1)将下一条指令的所在地址入栈
2)并将子程序的起始地址送入PC(于是CPU的下一条指令就会转去执行子程序).
GCC中的inline函数可以相当于在一个普通的全局函数加上inline属性。inline关键字仅仅是建议编译器在编译的时候做内联展开处理,而不是强制在gcc编译器中,编译器可以忽略这个建议的,某一些情况下编译器会自动忽略这个inline,将这个函数还原成普通函数。如果编译选项设置为负无穷,即使是inline函数也不会被内联展开,除非设置了强制内联展开的属性(attribute((always_inline))),即NS_INLINE
这个宏定义。
二、引用static
标示符
通常情况下使用是用作声明静态变量。
1)修饰局部变量的时候,让局部变量只初始化一次,局部变量在程序中只有一份内存,但是并不会改变局部变量的作用域,仅仅是改变了局部变量的生命周期(只到程序结束,这个局部变量才会销毁)。
2)修饰全局变量的时候,全局变量的作用域仅限于当前文件。
当修饰函数的时候,对函数的连接方式产生影响,使得函数只在本文件内部有效,对其他文件是不可见的。这样的函数又叫作静态函数。使用静态函数的好处是,不需要担心在其他文件存在同名的函数从而产生干扰。
如果想要其他文件可以引用本地函数,则要在函数定义时使用关键字extern,表示该函数是外部函数,可供其他文件调用。另外在要引用别的文件中定义的外部函数的文件中,使用extern声明要用的外部函数即可。
另外也因为使用了static修饰,从而保证了不会不断地调用copy,保证了函数地址的一致性,减小了内存压力。
三、没有使用以上两个标识符修饰
简单浏览一下这个函数,我们可以发现这个函数是一个单例方法的构造,相对比较少见的用法,相对简洁,本仍然是一个简单的函数方法,可以被外界用CG_EXTERN
调用。
用static修饰的函数,本限定在本源码文件中,不能被本源码文件以外的代码文件调用。而普通的函数,默认是extern的,也就是说,可以被其它代码文件调用该函数。
总结
一、为什么inline
函数能取代宏?
1)#define定义的函数要有特别的格式要求,并不是每个人都能熟练使用,而使用`inline`则就行平常写函数那样。
2)和其他的宏定义一样,使用define宏定义的代码,编译器不会对其进行参数有效性检查,很容易出现无法察觉的错误,调试过程中会出现很多麻烦。
3)不仅是输入类型,#define宏定义的代码,返回值不能被强制转换成可转换的适合的转换类 。
4)#define是文本替换,需要在预编译时展开,内联函数是编译时候展开。
二、inline
函数优点相比于普通函数:
1)inline函数避免了普通函数的,在汇编时必须调用call的缺点:取消了函数的参数压栈,减少了调用的开销,提高效率.所以执行速度确比一般函数的执行速度要快.
2)集成了宏的优点,使用时直接用代码替换(像宏一样);
三、inline内联函数的说明
1)内联函数只是我们向编译器提供的申请,编译器不一定采取inline形式调用函数。
2)内联函数不能承载大量的代码,如果内联函数的函数体过大,编译器会自动放弃内联。
3)内联函数内不允许使用循环语句或开关语句。
4)内联函数的定义须在调用之前。
5)当使用内联函数时,如果在多处调用了此内联函数,则此函数就会有N次代码段的拷贝,所以多配合`static`标示符使用。