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.png

3、unavailable

告诉编译器该方法不可用,如果强行调用编译器会提示错误。比如某个类在构造的时候不想直接通过init来初始化,只能通过特定的初始化方法()比如单例,就可以将init方法标记为unavailable;

官方的例子:

#define UNAVAILABLE_ATTRIBUTE __attribute__((unavailable))

自己写的例子 (加强记忆)

@interface AttributeTest : NSObject

- (instancetype)init __attribute__((unavailable("你不要用 -init"))); 

@end

强行调用,编译报错

image.png

4、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: +loadconstructor 调用的顺序。

__attribute__((constructor)) void  beforeMain(){
    NSLog(@">>>>>>>>>>>before main");
}

+ (void)load {
    NSLog(@">>>>>>>>>load");
}

输出结果:

testDemo[52892:3581563] >>>>>>>>>load
testDemo[52892:3581563] >>>>>>>>>>>before main

+loadconstructor先调用。

原因: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.png

7、cleanup

学习下:黑魔法attribute((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 单例。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,709评论 0 9
  • iOS宏的经典用法Apple的习惯attribute iOS宏的经典用法1.常量宏、表达式宏 define kTa...
    reallychao阅读 3,863评论 0 0
  • 前言 2000年,伊利诺伊大学厄巴纳-香槟分校(University of Illinois at Urbana-...
    星光社的戴铭阅读 15,888评论 8 180
  • GNU C的一大特色就是__attribute__机制。__attribute__可以设置函数属性(Functio...
    闭家锁阅读 17,271评论 0 5
  • 大 考 文/中流击水 沙沙笔纸响,考生上战场。 张张试卷里,题题测智商。 十年坐寒窗,功夫在平常。 ...
    楚山汉水阅读 358评论 7 8