iOS __attribute__ 的用法总结
_attribute_ 机制是GNU C 的一大特色, 是一个编译器指令,它指定声明的特征,允许更多的错误检查和高级优化。
格式:_attribute_(xxx) xxx:即参数
__ attribute__ 在iOS中的实际用法总结(部分常用关键词):
1、format
官方例子:NSLog
FOUNDATION_EXPORT void NSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2) NS_NO_TAIL_CALL;
#define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A)))
format属性可以给被声明的函数加上类似printf或者scanf的特征,它可以使编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。
对于format参数的使用如下:
format (archetype, string-index, first-to-check)
archetype指定哪种风格,这里是NSString
string-index指定传入的第几个参数是格式化字符串
first-to-check指定第一个可变参数所在的索引
自己写的例子(加强理解):
#define QYFormatFunc(a,b) __attribute__((format(__NSString__, a, b)))
FOUNDATION_EXPORT void QYLog(NSString *des, NSString * _Nonnull format, ...) QYFormatFunc(2,3);
#define QYLog(des,format, ...) NSLog([NSString stringWithFormat:@"%@%@",des, format], __VA_ARGS__)
调用
- (void)test0 {
QYLog(@"test0 = ",@"%@", @"0");
}
//输出》》》2018-11-08 14:33:56.854454+0800 testDemo[59161:32792950] test0 = 0
2、availability
官方例子:
- (CGSize)sizeWithFont:(UIFont *)font NS_DEPRECATED_IOS(2_0, 7_0, "Use -sizeWithAttributes:") __TVOS_PROHIBITED;
来看一下 后边的宏
#define NS_DEPRECATED_IOS(_iosIntro, _iosDep, ...) CF_DEPRECATED_IOS(_iosIntro, _iosDep, __VA_ARGS__)
define CF_DEPRECATED_IOS(_iosIntro, _iosDep, ...) __attribute__((availability(ios,introduced=_iosIntro,deprecated=_iosDep,message="" __VA_ARGS__)))
//宏展开以后如下
__attribute__((availability(ios,introduced=2_0,deprecated=7_0,message=""__VA_ARGS__)));
introduced:第一次出现的版本。
deprecated:声明要废弃的版本,意味着用户要迁移为其他API
obsoleted: 声明移除的版本,意味着完全移除,再也不能使用它
message:一些关于废弃和移除的额外信息,clang发出警告的时候会提供这些信息,对用户使用替代的API非常有用。
自己写的例子(加强理解)
- (void)oldMethod __attribute__((availability(ios,introduced=2_0,deprecated=7_0,obsoleted=13_0,message="用 -newMethod 这个方法替代 ")));
- (void)newMethod;
调用 oldMethod 会警告:
image.png3、unavailable
告诉编译器该方法不可用,如果强行调用编译器会提示错误。比如某个类在构造的时候不想直接通过init来初始化,只能通过特定的初始化方法()比如单例,就可以将init方法标记为unavailable;
官方的例子:
#define UNAVAILABLE_ATTRIBUTE __attribute__((unavailable))
自己写的例子 (加强记忆)
@interface AttributeTest : NSObject
- (instancetype)init __attribute__((unavailable("你不要用 -init")));
@end
强行调用,编译报错
image.png4、nonnull
编译器对函数参数进行NULL的检查,参数类型必须是指针类型(包括对象)
自己写的例子(同上)
- (NSString *)getString1:(NSString *)str __attribute__((nonnull (1)));
- (NSString *)getString2:(NSString * _Nonnull)str;
调用传参为空,会提示waring
image.png两种方式,效果相同。
5、constructor/ destructor
constructor修饰的函数会在main函数之前执行。destructor修饰的函数会在main函数之后执行,可以用exit模拟或者手动杀死程序。
__attribute__((constructor)) void beforeMain(){
NSLog(@">>>>>>>>>>>>>>before main");
}
__attribute__((destructor)) void afterMain() {
NSLog(@">>>>>>>>>>>>>>after main");
}
输出结果:
... testDemo[52322:3526219] >>>>>>>>>>>>>>before main
... testDemo[52322:3526219] >>>>>>>>>>>>>>main
... testDemo[52322:3526219] >>>>>>>>>>>>>>after main
假如需要多个被修饰的函数,还可以控制优先级,同一个文件有效。例如:
__attribute__((constructor(112))) void beforeMain112(){
NSLog(@">>>>>>>>>>>>>>before main112");
}
__attribute__((constructor(111))) void beforeMain111(){
NSLog(@">>>>>>>>>>>>>>before main111");
}
__attribute__((constructor(110))) void beforeMain110(){
NSLog(@">>>>>>>>>>>>>>before main110");
}
__attribute__((destructor(110))) void afterMain110() {
NSLog(@">>>>>>>>>>>>>>after main110");
}
__attribute__((destructor(111))) void afterMain111() {
NSLog(@">>>>>>>>>>>>>>after main111");
}
__attribute__((destructor(112))) void afterMain112() {
NSLog(@">>>>>>>>>>>>>>after main112");
}
输出结果:
testDemo[52665:3558499] >>>>>>>>>>>>>>before main110
testDemo[52665:3558499] >>>>>>>>>>>>>>before main111
testDemo[52665:3558499] >>>>>>>>>>>>>>before main112
testDemo[52665:3558499] >>>>>>>>>>>>>>main
testDemo[52665:3558499] >>>>>>>>>>>>>>after main112
testDemo[52665:3558499] >>>>>>>>>>>>>>after main111
testDemo[52665:3558499] >>>>>>>>>>>>>>after main110
括号内的值表示优先级,[0,100]这个返回时系统保留。
执行顺序constructor从小到大,destructor从大到小。
PS: +load
和 constructor
调用的顺序。
__attribute__((constructor)) void beforeMain(){
NSLog(@">>>>>>>>>>>before main");
}
+ (void)load {
NSLog(@">>>>>>>>>load");
}
输出结果:
testDemo[52892:3581563] >>>>>>>>>load
testDemo[52892:3581563] >>>>>>>>>>>before main
+load
比 constructor
先调用。
原因:dyld(动态链接器,程序的最初起点)在加载 image(可以理解成 Mach-O 文件)时会先通知 objc runtime 去加载其中所有的类,每加载一个类时,它的 +load 随之调用,全部加载完成后,dyld 才会调用这个 image 中所有的 constructor 方法,然后才调用main函数.
6、enable_if
用来检查C方法参数是否合法,只能用来修饰函数:
static void printValidAge(int age)
__attribute__((enable_if(age > 0 && age < 120, "仙人???"))) {
printf("%d", age);
}
表示只能输入的参数只能是 0 ~ 120,否则编译报错
test0@2x.png7、cleanup
8、objc_runtime_name
用于 @interface 或 @protocol,将类或协议的名字在编译时指定成另一个:
__attribute__((objc_runtime_name("SarkGay")))
@interface Sark : NSObject
@end
NSLog(@"%@", NSStringFromClass([Sark class])); // "SarkGay"
所有直接使用这个类名的地方都会被替换(唯一要注意的是这时用反射就不对了),最简单粗暴的用处就是去做个类名混淆:
__attribute__((objc_runtime_name("40ea43d7629d01e4b8d6289a132482d0dd5df4fa")))
@interface SecretClass : NSObject
@end
还能用数字开头,怕不怕 - -,假如写个脚本把每个类前加个随机生成的 objc_runtime_name,岂不是最最精简版的代码混淆就完成了呢…
它是我所了解的唯一一个对 objc 运行时类结构有影响的 attribute,通过编码类名可以在编译时注入一些信息,被带到运行时之后,再反解出来,这就相当于开设了一条秘密通道,打通了写码时和运行时。脑洞一下,假如把这个 attribute 定义成宏,以 annotation 的形式完成某些功能,比如:
// @singleton 包裹了 __attribute__((objc_runtime_name(...)))
// 将类名改名成 "SINGLETON_Sark_sharedInstance"
@singleton(Sark, sharedInstance)
@interface Sark : NSObject
+ (instancetype)sharedInstance;
@end
在运行时用 attribute((constructor)) 获取入口时机,用 runtime 找到这个类,反解出 “sharedInstance” 这个 selector 信息,动态将 + alloc,- init 等方法替换,返回 + sharedInstance 单例。