阅读 315

OC中的静态(static)/ 内联(inline)函数

起因

项目中用到了YYKit中的一些组件,比如YYTextYYImage,于是抽了点时间阅读了一下当中的一些代码。

发现了在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`标示符使用。
复制代码

Refrence

另外

关注下面的标签,发现更多相似文章
评论