第0部分:说在前面的话
使用Objective-C作为主要开发语言已经有一段时间了,其独特的语法相信也令初次接触她的人印象颇深。然而语法之外,当我们查看苹果提供的头文件,我还被另外一些符号所吸引,比如 “NS_DESIGNATED_INITIALIZER”、“API_AVAILABLE”等等。这些就是传说中的__attribute__(编译属性)。虽然从字面上很好理解其含义,也能猜到他们是一些与编译器相关的指令,但直到最近,我才终于搞清其背后的原理与历史。
这篇文章,就简单总结下 __attribute__的背后的一些编译器相关的知识与常见编译属性用法。
首先,引用一句比较正式的定义:
__attribute__is a compiler directive that specifies characteristics on declarations, which allows for more error checking and advanced optimizations.
实际上,__attribute__是GNU C 的特点之一,在这篇GNU的官方文档中有比较详细的介绍。
简单总结下:_attribute__有多种类型,Function-Attributes、Variable-Attributes、Type-Attributes 、Label Attributes和 Enumerator Attributes。
书写特征是:__attribute__前后都有两个下划线,并切后面会紧跟一对原括弧,括弧里面是相应的__attribute__参数。
语法格式为:__attribute__ ((attribute-list))
位置约束为:放于声明的尾部“;”之前。
而iOS目前的编译器llvm/Clang ,不仅对__attribute__提供了非常良好的支持,同时还增加了一些扩展,某些attribute甚至在iOS开发中起到了不可或缺的作用,下面就总结下在iOS开发中,比较常见和有趣的一些__attribute__用法:
第一部分:开发中比较常见的:
1、deprecated
在iOS中,常见的NS_DEPRECATED()宏实际就是__attribute__((deprecated)),还可以增加一些提示语变为__attribute__((deprecated(s)))。
2、availability
3、overloadable
Clang provides support for C++ function overloading in C. Function overloading in C is introduced using theoverloadableattribute. For example, one might provide several overloaded versions of atgsinfunction that invokes the appropriate standard function computing thesineof a value withfloat,double, orlong doubleprecision:
#include float__attribute__((overloadable))tgsin(floatx){returnsinf(x);}double__attribute__((overloadable))tgsin(doublex){returnsin(x);}longdouble__attribute__((overloadable))tgsin(longdoublex){returnsinl(x);}
4、format (gcc)
Theformatattribute specifies that a function takesprintf,scanf,strftimeorstrfmonstyle arguments which should be type-checked against a format string.
比如:
externintmy_printf(void*my_object,constchar*my_format,...)__attribute__((format(printf,2,3)));
5、nonnull (gcc)
extern void*my_memcpy(void*dest,constvoid*src,size_tlen)__attribute__((nonnull(1,2)));
iOS中对于nonnull使用不多,更为常见的是苹果在Xcode 6.3引入的一个Objective-C的新特性:nullability annotations
6、unused
This attribute, attached to a function, means that the function is meant to be possibly unused. GCC will not produce a warning for this function.
简而言之,可以用它来避免编译器产生unused警告。
7、unavailable
用法:__attribute__((unavailable)),NS_UNAVAILABLE
这个挺实用,它的作用是告诉编译器该方法不可用,如果强行调用编译器会提示错误。比如某个类在构造的时候不想直接通过init来初始化,只能通过特定的初始化方法,就可以将init方法标记为unavailable。
其他常见的还有很多,不一一列举了,详见https://clang.llvm.org/docs/AttributeReference.html
第二部分:开发中不常使用,但恰当使用能起到事半功倍的效果:
1、__attribute__((objc_requires_super))
aka: NS_REQUIRES_SUPER,标志子类继承这个方法时需要调用 super,否则给出编译警告。
2、objc_subclassing_restricted
用来定义一个 Final Class
3、constructor / destructor
(该部分引用自https://blog.sunnyxx.com/2016/05/14/clang-attributes/)顾名思义,构造器和析构器,加上这两个属性的函数会在分别在可执行文件(或 shared library)load和 unload 时被调用,可以理解为在 main() 函数调用前和 return 后执行:
__attribute__((constructor))
static void beforeMain(void) {
NSLog(@"beforeMain");
}
__attribute__((destructor))
static void afterMain(void) {
NSLog(@"afterMain");
}
int main(int argc, const char * argv[]) {
NSLog(@"main");
return 0;
}
// Console:
// "beforeMain" -> "main" -> "afterMain"
constructor 和 +load 都是在 main 函数执行前调用,但 +load 比 constructor 更加早一些,因为 dyld(动态链接器,程序的最初起点)在加载 image(可以理解成 Mach-O 文件)时会先通知 objc runtime 去加载其中所有的类,每加载一个类时,它的 +load 随之调用,全部加载完成后,dyld 才会调用这个 image 中所有的 constructor 方法。
所以 constructor 是一个干坏事的绝佳时机:
所有 Class 都已经加载完成
main 函数还未执行
无需像 +load 还得挂载在一个 Class 中
PS:若有多个 constructor 且想控制优先级的话,可以写成 __attribute__((constructor(101))),里面的数字越小优先级越高,1 ~ 100 为系统保留。
4、objc_runtime_name
这个属性就非常有意思了,用于 @interface 或 @protocol,将类或协议的名字在编译时指定成另一个。
之前在项目中经常有一些需求需要修改类名,通过这个__attribute__只需一行代码就可以直接在完成了,awesome!但需要注意的是如果代码中有用反射就会出问题。或者做一些简单的类名混淆:
__attribute__((objc_runtime_name("abcdefghighlmn")))
@interface SecretClass : NSObject
@end
5、cleanup
作用:离开作用域之后执行指定的方法。实际应用中可以在作用域结束之后做一些特定的工作,比如清理。
Reactive Cocoa 用这个特性实现了神奇的 @onExit,关于这个 attribute,在sunnyxx的这篇博客中有非常详细的介绍。
6、objc_designated_initializer (NS_DESIGNATED_INITIALIZER)
aka:NS_DESIGNATED_INITIALIZER,这也是一个在iOS中非常常见的attribute,它可以指定类的初始化方法。指定初识化方法并不是对使用者。而是对内部的现实。
具体的讲解可以参见这篇文章:https://segmentfault.com/a/1190000004116196
这个attribute的目的其实是在初始化类的实例时,无论调用关系如何复杂,必须调用一次该类的Designated intializer(可以有多个),对于 Designated intializer,必须调用父类的Designated intializer。对于父类的父类这个规则亦然,对Designated intializer的调用一直要到根类。
7、objc_boxable
OC中常见的 @(10)这样的写法,它的实现方式就是利用该Function attributes:
struct __attribute__((objc_boxable)) some_struct {
int i;
};
union __attribute__((objc_boxable)) some_union {
int i;
float f;
};
typedef struct __attribute__((objc_boxable)) _some_struct some_struct;
some_struct ss;
NSValue *boxed = @(ss);
8、一些与内存管理相关的__attribute__
__attribute__((objc_precise_lifetime))
用这个attribute修饰一个变量,防止ARC过早释放它。即向编译器表明,变量在它所处的整个作用域内,都应被认为是有用的。
Foundation里提供了宏NS_VALID_UNTIL_END_OF_SCOPE。
__attribute__((objc_returns_inner_pointer))
用于修饰一个方法,防止ARC过早释放这个方法的所有者。Foundation里提供了宏NS_RETURNS_INNER_POINTER。
__attribute__((ns_returns_retained))
在Objective-C里,很多方法的命名都是有约定的含义的。比如以alloc、new、copy、mutableCopy开头的方法,会返回一个retain count为1的对象。对于那些未遵守这些约定,却做了类似操作的方法,可以使用__attribute__((ns_returns_retained))来标明,但如果方法遵守了命名约定,就不必再修饰。
还有很多,不一一赘述,这篇文章中有较为细致的讲解:(传送门)。
结语:
尽管本文列举了很多attribute,但它们仍然只是GCC/Clang中有关attribute的一小部分。
尽管在代码中我们可以从不使用attribute,但了解和使用attribute仍然会带给我们巨大的收益:
这不仅仅体现在通过合理使用attribute,做到更好的编译器优化、编译器检查、内存管理、自动代码生成等;
更重要的是通过这些attribute,能够使得代码的可读性、可维护性得到大大提高,让后继者能拥有一份赏心悦目的代码未尝不是一件值得高兴的事呢。
参考资料:
https://nshipster.com/__attribute__/
http://www.unixwiz.net/techtips/gnu-c-attributes.html
https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html
https://gcc.gnu.org/onlinedocs/gcc-4.0.4/gcc/Type-Attributes.html
https://clang.llvm.org/docs/AttributeReference.html
https://blog.sunnyxx.com/2016/05/14/clang-attributes/
http://blog.sunnyxx.com/2014/09/15/objc-attribute-cleanup/
https://segmentfault.com/a/1190000003887934
https://medium.com/@liushuaikobe/%E4%B8%8Eclang%E5%85%B1%E8%88%9E-attribute-8b6bc839958c