attribute是GNU C特色之一,在iOS用的比較廣泛.系統中有許多地方使用到. attribute可以設置函數屬性(Function Attribute )、變量屬性(Variable Attribute )和類型屬性(Type Attribute)等.
函數屬性(Function Attribute)
- noreturn
- noinline
- always_inline
- pure
- const
- nothrow
- sentinel
- format
- format_arg
- no_instrument_function
- section
- constructor
- destructor
- used
- unused
- deprecated
- weak
- malloc
- alias
- warn_unused_result
- nonnull
類型屬性(Type Attributes)
- aligned
- packed
- transparent_union,
- unused,
- deprecated
- may_alias
變量屬性(Variable Attribute)
- aligned
- packed
Clang特有的
- availability
- overloadable
書寫格式
書寫格式:attribute后面會緊跟一對原括弧,括弧里面是相應的attribute參數
__attribute__(xxx)
常見的系統用法
format
官方例子:NSLog
#define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A)))
format屬性可以給被聲明的函數加上類似printf或者scanf的特征,它可以使編譯器檢查函數聲明和函數實際調用參數之間的格式化字符串是否匹配。該功能十分有用,尤其是處理一些很難發現的bug。對於format參數的使用如下
format (archetype, string-index, first-to-check)
第一參數需要傳遞“archetype”指定是哪種風格,這里是 NSString;“string-index”指定傳入函數的第幾個參數是格式化字符串;“first-to-check”指定第一個可變參數所在的索引.
noreturn
官方例子: abort() 和 exit()
該屬性通知編譯器函數從不返回值。當遇到類似函數還未運行到return語句就需要退出來的情況,該屬性可以避免出現錯誤信息。
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__))); //ios即是iOS平台 //introduced 從哪個版本開始使用 //deprecated 從哪個版本開始棄用 //message 警告的消息
availability屬性是一個以逗號為分隔的參數列表,以平台的名稱開始,包含一些放在附加信息里的一些里程碑式的聲明。
-
introduced:第一次出現的版本。
-
deprecated:聲明要廢棄的版本,意味着用戶要遷移為其他API
-
obsoleted: 聲明移除的版本,意味着完全移除,再也不能使用它
-
unavailable:在這些平台不可用
-
message:一些關於廢棄和移除的額外信息,clang發出警告的時候會提供這些信息,對用戶使用替代的API非常有用。
-
這個屬性支持的平台:ios,macosx。
簡單例子:
//如果經常用,建議定義成類似系統的宏 - (void)oldMethod:(NSString *)string __attribute__((availability(ios,introduced=2_0,deprecated=7_0,message="用 -newMethod: 這個方法替代 "))){ NSLog(@"我是舊方法,不要調我"); } - (void)newMethod:(NSString *)string{ NSLog(@"我是新方法"); }
效果:

//如果調用了,會有警告

unavailable
告訴編譯器該方法不可用,如果強行調用編譯器會提示錯誤。比如某個類在構造的時候不想直接通過init來初始化,只能通過特定的初始化方法()比如單例,就可以將init方法標記為unavailable;
//系統的宏,可以直接拿來用 #define UNAVAILABLE_ATTRIBUTE __attribute__((unavailable)) #define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE
@interface Person : NSObject @property(nonatomic,copy) NSString *name; @property(nonatomic,assign) NSUInteger age; - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithName:(NSString *)name age:(NSUInteger)age; @end

//實際上unavailable后面可以跟參數,顯示一些信息,如:
//系統的 #define NS_AUTOMATED_REFCOUNT_UNAVAILABLE __attribute__((unavailable("not available in automatic reference counting mode")))
objc_root_class
表示這個類是一個根類(基類),比如NSObject,NSProxy.
//摘自系統 //NSProxy NS_ROOT_CLASS @interface NSProxy <NSObject> { Class isa; } //NSObject __OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0) OBJC_ROOT_CLASS OBJC_EXPORT @interface NSObject <NSObject> { Class isa OBJC_ISA_AVAILABILITY; }
NSObject
@property (nonatomic,strong) __attribute__((NSObject)) CFDictionaryRef myDictionary;
CFDictionaryRef屬於CoreFoundation框架的,也就是非OC對象,加上attribute((NSObject))后,myDictionary的內存管理會被當做OC對象來對待.
objc_designated_initializer
用來修飾類的designated initializer初始化方法,如果修飾的方法里沒有調用super類的 designated initializer,編譯器會發出警告。可以簡寫成NS_DESIGNATED_INITIALIZER
這篇文章講的很好,建議參考這個.
https://yq.aliyun.com/articles/5847
visibility
語法:
__attribute__((visibility("visibility_type")))
其中,visibility_type 是下列值之一:
-
default
假定的符號可見性可通過其他選項進行更改。缺省可見性將覆蓋此類更改。缺省可見性與外部鏈接對應。 -
hidden
該符號不存放在動態符號表中,因此,其他可執行文件或共享庫都無法直接引用它。使用函數指針可進行間接引用。 -
internal
除非由 特定於處理器的應用二進制接口 (psABI) 指定,否則,內部可見性意味着不允許從另一模塊調用該函數。 -
protected
該符號存放在動態符號表中,但定義模塊內的引用將與局部符號綁定。也就是說,另一模塊無法覆蓋該符號。 -
除指定 default 可見性外,此屬性都可與在這些情況下具有外部鏈接的聲明結合使用。
您可在 C 和 C++ 中使用此屬性。在 C++ 中,還可將它應用於類型、成員函數和命名空間聲明。
系統用法:
// UIKIT_EXTERN extern #ifdef __cplusplus #define UIKIT_EXTERN extern "C" __attribute__((visibility ("default"))) #else #define UIKIT_EXTERN extern __attribute__((visibility ("default"))) #endif
nonnull
編譯器對函數參數進行NULL的檢查,參數類型必須是指針類型(包括對象)
//使用
- (int)addNum1:(int *)num1 num2:(int *)num2 __attribute__((nonnull (1,2))){//1,2表示第一個和第二個參數不能為空 return *num1 + *num2; } - (NSString *)getHost:(NSURL *)url __attribute__((nonnull (1))){//第一個參數不能為空 return url.host; }
常見用法
aligned
__attribute((aligned (n))),讓所作用的結構成員對齊在n字節自然邊界上。如果結構中有成員的長度大於n,則按照最大成員的長度來對齊.例如:
不加修飾的情況
typedef struct { char member1; int member2; short member3; }Family; //輸出字節: NSLog(@"Family size is %zd",sizeof(Family)); //輸出結果為: 2016-07-25 10:28:45.380 Study[917:436064] Family size is 12
//修改字節對齊為1
typedef struct { char member1; int member2; short member3; }__attribute__ ((aligned (1))) Family; //輸出字節: NSLog(@"Family size is %zd",sizeof(Family)); //輸出結果為: 2016-07-25 10:28:05.315 Study[914:435764] Family size is 12
和上面的結果一致,因為 設定的字節對齊為1.而結構體中成員的最大字節數是int 4個字節,1 < 4,按照4字節對齊,和系統默認一致.
修改字節對齊為8
typedef struct { char member1; int member2; short member3; }__attribute__ ((aligned (8))) Family; //輸出字節: NSLog(@"Family size is %zd",sizeof(Family)); //輸出結果為: 2016-07-25 10:28:05.315 Study[914:435764] Family size is 16
這里 8 > 4,按照8字節對齊,結果為16,不知道字節對齊的可以看我的這篇文章http://www.jianshu.com/p/f69652c7df99
可是想了半天,也不知道這玩意有什么用,設定值小於系統默認的,和沒設定一樣,設定大了,又浪費空間,效率也沒提高,感覺學習學習就好.
packed
讓指定的結構結構體按照一字節對齊,測試:
//不加packed修飾 typedef struct { char version; int16_t sid; int32_t len; int64_t time; } Header; //計算長度 NSLog(@"size is %zd",sizeof(Header)); 輸出結果為: 2016-07-22 11:53:47.728 Study[14378:5523450] size is 16
可以看出,默認系統是按照4字節對齊
//加packed修飾 typedef struct { char version; int16_t sid; int32_t len; int64_t time; }__attribute__ ((packed)) Header; //計算長度 NSLog(@"size is %zd",sizeof(Header)); 輸出結果為: 2016-07-22 11:57:46.970 Study[14382:5524502] size is 15
用packed修飾后,變為1字節對齊,這個常用於與協議有關的網絡傳輸中.
noinline & always_inline
內聯函數:內聯函數從源代碼層看,有函數的結構,而在編譯后,卻不具備函數的性質。內聯函數不是在調用時發生控制轉移,而是在編譯時將函數體嵌入在每一個調用處。編譯時,類似宏替換,使用函數體替換調用處的函數名。一般在代碼中用inline修飾,但是能否形成內聯函數,需要看編譯器對該函數定義的具體處理
- noinline 不內聯
- always_inline 總是內聯
- 這兩個都是用在函數上
內聯的本質是用代碼塊直接替換掉函數調用處,好處是:快代碼的執行,減少系統開銷.適用場景:
- 這個函數更小
- 這個函數不被經常調用
使用例子:
//函數聲明 void test(int a) __attribute__((always_inline));
warn_unused_result
當函數或者方法的返回值很重要時,要求調用者必須檢查或者使用返回值,否則編譯器會發出警告提示
- (BOOL)availiable __attribute__((warn_unused_result)) { return 10; }
警告如下:

objc_subclassing_restricted
因為某些原因,我們不希望這個類被繼承,也就是 "最終"的類,用法如下:
__attribute__((objc_subclassing_restricted))
@interface ViewController : UIViewController @end
如果繼承了這個類,編譯器會報錯

objc_requires_super
這個屬性要求子類在重寫父類的方法時,必須要重載父類方法,也就是調用super方法,否則警告.示例如下:
@interface ViewController : UIViewController - (void)jump __attribute__((objc_requires_super)); @end - (void)jump{ NSLog(@"父類必須先執行"); } @interface SGViewController : ViewController @end @implementation SGViewController - (void)jump{ NSLog(@"子類才能再執行"); } @end
警告如下:

objc_boxable
實現類似於NSNumber 的快速打包能力@(...),一般對於struct,union我們只能通過NSValue將其打包. objc_boxable 可以幫助我們實現快速打包,示例如下:
//自定義結構體 typedef struct __attribute__((objc_boxable)){ CGFloat x,y,width,height; }SGRect; SGRect rect = {0,0,100,200}; //這里直接打包成NSValue NSValue *value = @(rect); //這里我直接用系統的方法打印 NSLog(@"%@",NSStringFromCGRect(value.CGRectValue)); 輸出: 2016-07-21 21:28:43.538 Study[14118:5408921] {{0, 0}, {100, 200}}
這樣SGRect就具備快速打包功能了.
constructor / destructor
意思是: 構造器和析構器;constructor修飾的函數會在main函數之前執行,destructor修飾的函數會在程序exit前調用.
示例如下:
int main(int argc, char * argv[]) { @autoreleasepool { NSLog(@"main"); return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } __attribute__((constructor)) void before(){ NSLog(@"before main"); } __attribute__((destructor)) void after(){ NSLog(@"after main"); } //在viewController中調用exit - (void)viewDidLoad { [super viewDidLoad]; exit(0); } 輸出如下: 2016-07-21 21:49:17.446 Study[14162:5415982] before main 2016-07-21 21:49:17.447 Study[14162:5415982] main 2016-07-21 21:49:17.534 Study[14162:5415982] after main
注意點:
- 程序退出的時候才會調用after函數,經測試,手動退出程序會執行
- 上面兩個函數不管寫在哪個類里,哪個文件中效果都一樣
- 如果存在多個修飾的函數,那么都會執行,順序不定
實際上如果存在多個修飾過的函數,可以它們的調整優先級
代碼如下:
int main(int argc, char * argv[]) { @autoreleasepool { NSLog(@"main"); return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } __attribute__((constructor(101))) void before1(){ NSLog(@"before main - 1"); } __attribute__((constructor(102))) void before2(){ NSLog(@"before main - 2"); } __attribute__((destructor(201))) void after1(){ NSLog(@"after main - 1"); } __attribute__((destructor(202))) void after2(){ NSLog(@"after main - 2"); } 輸出結果如下: 2016-07-21 21:59:35.622 Study[14171:5418393] before main - 1 2016-07-21 21:59:35.624 Study[14171:5418393] before main - 2 2016-07-21 21:59:35.624 Study[14171:5418393] main 2016-07-21 21:59:35.704 Study[14171:5418393] after main - 2 2016-07-21 21:59:35.704 Study[14171:5418393] after main - 1
注意點:
- 括號內的值表示優先級,[0,100]這個返回時系統保留的,自己千萬別調用.
- 根據輸出結果可以看出,main函數之前的,數值越小,越先調用;main函數之后的數值越大,越先調用.
當函數聲明和函數實現分開寫時,格式如下:
static void before() __attribute__((constructor)); static void before() { printf("before\n"); }
討論:+load,constructor,main的執行順序,代碼如下:
+ (void)load{ NSLog(@"load"); } __attribute__((constructor)) void before(){ NSLog(@"before main"); } 輸出結果如下: 2016-07-21 22:13:58.591 Study[14185:5421811] load 2016-07-21 22:13:58.592 Study[14185:5421811] before main 2016-07-21 22:13:58.592 Study[14185:5421811] main
可以看出執行順序為:
load->constructor->main
為什么呢?
因為 dyld(動態鏈接器,程序的最初起點)在加載 image(可以理解成 Mach-O 文件)時會先通知 objc runtime 去加載其中所有的類,每加載一個類時,它的 +load 隨之調用,全部加載完成后,dyld 才會調用這個 image 中所有的 constructor 方法,然后才調用main函數.
enable_if
用來檢查參數是否合法,只能用來修飾函數:
void printAge(int age) __attribute__((enable_if(age > 0 && age < 120, "你丫太監?"))) { NSLog(@"%d",age); }
表示只能輸入的參數只能是 0 ~ 120左右,否則編譯報錯
報錯如下:

cleanup
聲明到一個變量上,當這個變量作用域結束時,調用指定的一個函數.如果不知道什么是作用域,請先學習一下.例子:
//這里傳遞的參數是變量的地址 void intCleanup(int *num){ NSLog(@"cleanup------%d",*num); } - (void)test{ int a __attribute__((cleanup(intCleanup))) = 10; } 輸出結果為: 2016-07-22 09:59:09.139 Study[14293:5495713] cleanup------10
注意點:
- 指定的函數傳遞的參數是變量的地址
- 作用域的結束包括:大括號結束、return、goto、break、exception等情況
- 當作用域內有多個cleanup的變量時,遵守 先入后出 的棧式結構.
示例代碼:
void intCleanup(int *num){ NSLog(@"cleanup------%d",*num); } void stringCleanup(NSString **str){ NSLog(@"cleanup------%@",*str); } void rectCleanup(CGRect *rect){ CGRect temp = *rect; NSString *str = NSStringFromCGRect(temp); NSLog(@"cleanup------%@",str); } int a __attribute__((cleanup(intCleanup))) = 10; { NSString *string __attribute__((cleanup(stringCleanup))) = @"string"; CGRect rect __attribute__((cleanup(rectCleanup))) = {0,0,1,1}; } 輸出結果為: 2016-07-22 10:09:36.621 Study[14308:5498861] cleanup------{{0, 0}, {1, 1}} 2016-07-22 10:09:36.622 Study[14308:5498861] cleanup------string 2016-07-22 10:09:36.622 Study[14308:5498861] cleanup------10
討論:如果修飾了某個對象,那么cleanup和dealloc,誰先執行?
測試代碼如下:
void objectCleanup(NSObject **obj){ NSLog(@"cleanup------%@",*obj); } - (void)viewDidLoad { [super viewDidLoad]; ViewController *vc __attribute__((cleanup(objectCleanup))) = [[ViewController alloc] init]; } - (void)dealloc{ NSLog(@"dealloc"); } 輸出結果如下: 2016-07-22 10:23:08.839 Study[14319:5502769] cleanup------<ViewController: 0x13fe881e0> 2016-07-22 10:23:08.840 Study[14319:5502769] dealloc
可以明顯看出,cleanup先於對象的dealloc執行.
- 在block中的用法:在block中使用,先看例子:
//指向block的指針,覺得不好理解可以用typeof void blockCleanUp(void(^*block)()){ (*block)(); } void (^block)(void) __attribute__((cleanup(blockCleanUp))) = ^{ NSLog(@"finish block"); };
這個好處就是,不用等到block最后才寫某些代碼,我們可以把它放在block的任意位置,防止忘記.
overloadable
用於c語言函數,可以定義若干個函數名相同,但參數不同的方法,調用時編譯器會自動根據參數選擇函數原型:
__attribute__((overloadable)) void print(NSString *string){ NSLog(@"%@",string); } __attribute__((overloadable)) void print(int num){ NSLog(@"%d",num); } //調用 print(10); print(@"哈哈");
objc_runtime_name
看到runtime是不是就感覺高大上,沒錯這個也跟運行時有關.作用是將將類或協議的名字在編譯時指定成另一個.示例如下:
__attribute__((objc_runtime_name("NSObject"))) @interface SGObject :NSObject @end //調用 NSLog(@"%@",[SGObject class]); //輸出 2016-07-22 11:18:00.934 Study[14355:5516261] NSObject
可以用來做代碼混淆.
更多請看官網:
https://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Function-Attributes.html
轉載自:
https://www.jianshu.com/p/29eb7b5c8b2d