_attribute_ 機制是GNU C 的一大特色, 是一個編譯器指令,它指定聲明的特征,允許更多的錯誤檢查和高級優(yōu)化。
格式:_attribute_(xxx) xxx:即參數(shù)
__ attribute__ 在iOS中的實際用法總結(jié)(部分常用關(guān)鍵詞):
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屬性可以給被聲明的函數(shù)加上類似printf或者scanf的特征,它可以使編譯器檢查函數(shù)聲明和函數(shù)實際調(diào)用參數(shù)之間的格式化字符串是否匹配。
對于format參數(shù)的使用如下:
format (archetype, string-index, first-to-check)
archetype指定哪種風(fēng)格,這里是NSString
string-index指定傳入的第幾個參數(shù)是格式化字符串
first-to-check指定第一個可變參數(shù)所在的索引
自己寫的例子(加強理解):
#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__)
調(diào)用
- (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:第一次出現(xiàn)的版本。
deprecated:聲明要廢棄的版本,意味著用戶要遷移為其他API
obsoleted: 聲明移除的版本,意味著完全移除,再也不能使用它
message:一些關(guān)于廢棄和移除的額外信息,clang發(fā)出警告的時候會提供這些信息,對用戶使用替代的API非常有用。
自己寫的例子(加強理解)
- (void)oldMethod __attribute__((availability(ios,introduced=2_0,deprecated=7_0,obsoleted=13_0,message="用 -newMethod 這個方法替代 ")));
- (void)newMethod;
調(diào)用 oldMethod 會警告:

3、unavailable
告訴編譯器該方法不可用,如果強行調(diào)用編譯器會提示錯誤。比如某個類在構(gòu)造的時候不想直接通過init來初始化,只能通過特定的初始化方法()比如單例,就可以將init方法標(biāo)記為unavailable;
官方的例子:
#define UNAVAILABLE_ATTRIBUTE __attribute__((unavailable))
自己寫的例子 (加強記憶)
@interface AttributeTest : NSObject
- (instancetype)init __attribute__((unavailable("你不要用 -init")));
@end
強行調(diào)用,編譯報錯

4、nonnull
編譯器對函數(shù)參數(shù)進行NULL的檢查,參數(shù)類型必須是指針類型(包括對象)
自己寫的例子(同上)
- (NSString *)getString1:(NSString *)str __attribute__((nonnull (1)));
- (NSString *)getString2:(NSString * _Nonnull)str;
調(diào)用傳參為空,會提示waring

兩種方式,效果相同。
5、constructor/ destructor
constructor修飾的函數(shù)會在main函數(shù)之前執(zhí)行。destructor修飾的函數(shù)會在main函數(shù)之后執(zhí)行,可以用exit模擬或者手動殺死程序。
__attribute__((constructor)) void beforeMain(){
NSLog(@">>>>>>>>>>>>>>before main");
}
__attribute__((destructor)) void afterMain() {
NSLog(@">>>>>>>>>>>>>>after main");
}
輸出結(jié)果:
... testDemo[52322:3526219] >>>>>>>>>>>>>>before main
... testDemo[52322:3526219] >>>>>>>>>>>>>>main
... testDemo[52322:3526219] >>>>>>>>>>>>>>after main
假如需要多個被修飾的函數(shù),還可以控制優(yōu)先級,同一個文件有效。例如:
__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");
}
輸出結(jié)果:
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
括號內(nèi)的值表示優(yōu)先級,[0,100]這個返回時系統(tǒng)保留。
執(zhí)行順序constructor從小到大,destructor從大到小。
PS: +load 和 constructor 調(diào)用的順序。
__attribute__((constructor)) void beforeMain(){
NSLog(@">>>>>>>>>>>before main");
}
+ (void)load {
NSLog(@">>>>>>>>>load");
}
輸出結(jié)果:
testDemo[52892:3581563] >>>>>>>>>load
testDemo[52892:3581563] >>>>>>>>>>>before main
+load 比 constructor先調(diào)用。
原因:dyld(動態(tài)鏈接器,程序的最初起點)在加載 image(可以理解成 Mach-O 文件)時會先通知 objc runtime 去加載其中所有的類,每加載一個類時,它的 +load 隨之調(diào)用,全部加載完成后,dyld 才會調(diào)用這個 image 中所有的 constructor 方法,然后才調(diào)用main函數(shù).
6、enable_if
用來檢查C方法參數(shù)是否合法,只能用來修飾函數(shù):
static void printValidAge(int age)
__attribute__((enable_if(age > 0 && age < 120, "仙人???"))) {
printf("%d", age);
}
表示只能輸入的參數(shù)只能是 0 ~ 120,否則編譯報錯

7、cleanup
學(xué)習(xí)下:黑魔法attribute((cleanup))
8、objc_runtime_name
用于 @interface 或 @protocol,將類或協(xié)議的名字在編譯時指定成另一個:
__attribute__((objc_runtime_name("SarkGay")))
@interface Sark : NSObject
@end
NSLog(@"%@", NSStringFromClass([Sark class])); // "SarkGay"
所有直接使用這個類名的地方都會被替換(唯一要注意的是這時用反射就不對了),最簡單粗暴的用處就是去做個類名混淆:
__attribute__((objc_runtime_name("40ea43d7629d01e4b8d6289a132482d0dd5df4fa")))
@interface SecretClass : NSObject
@end
還能用數(shù)字開頭,怕不怕 - -,假如寫個腳本把每個類前加個隨機生成的 objc_runtime_name,豈不是最最精簡版的代碼混淆就完成了呢…
它是我所了解的唯一一個對 objc 運行時類結(jié)構(gòu)有影響的 attribute,通過編碼類名可以在編譯時注入一些信息,被帶到運行時之后,再反解出來,這就相當(dāng)于開設(shè)了一條秘密通道,打通了寫碼時和運行時。腦洞一下,假如把這個 attribute 定義成宏,以 annotation 的形式完成某些功能,比如:
// @singleton 包裹了 __attribute__((objc_runtime_name(...)))
// 將類名改名成 "SINGLETON_Sark_sharedInstance"
@singleton(Sark, sharedInstance)
@interface Sark : NSObject
+ (instancetype)sharedInstance;
@end
在運行時用 attribute((constructor)) 獲取入口時機,用 runtime 找到這個類,反解出 “sharedInstance” 這個 selector 信息,動態(tài)將 + alloc,- init 等方法替換,返回 + sharedInstance 單例。