本文目錄
- iOS宏的經典用法
- Apple的習慣
- __attribute__
iOS宏的經典用法
1.常量宏、表達式宏
#define kTabBarH (49.0f)
#define kScreenH [UIScreen mainScreen].bounds.size.height
#define isScreenWidthEqual320 (fabs([UIScreen mainScreen].bounds.size.width - 320) < DBL_EPSILON)
2.帶參數的宏
// 例子1:同樣是一個表達式
#define isNilOrNull(obj) (obj == nil || [obj isEqual:[NSNull null]])
// 例子2:也可以沒有參數值 使用的時候要加上"()",是在使用的時候單獨成為一行語句的宏
#define MKAssertMainThread() NSAssert([NSThread isMainThread], @"This method must be called on the main thread")
3.函數宏(是一個沒有返回值的代碼塊,通常當做一行語句使用)
// do {} while(0) 一般是沒有返回值的,僅僅是代碼塊而已,使用do {} while(0)意思是讓它執行一次,相當於直接調用代碼塊
#define NSAssert(condition, desc, ...) \
do { \
__PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
if (!(condition)) { \
NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \
__assert_file__ = __assert_file__ ? __assert_file__ : @"<Unknown File>"; \
[[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \
object:self file:__assert_file__ \
lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \
} \
__PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
} while(0)
4.內聯函數 (一般有返回值)
CG_INLINE CGRect
CGRectMake(CGFloat x, CGFloat y, CGFloat width, CGFloat height)
{
CGRect rect;
rect.origin.x = x; rect.origin.y = y;
rect.size.width = width; rect.size.height = height;
return rect;
}
NS_INLINE BOOL
MKIsLeapYear() {
NSCalendar *calendar = [NSCalendar currentCalendar];
NSInteger year = [calendar component:NSCalendarUnitYear fromDate:[NSDate new]];
return year % 100 == 0 ? year % 400 == 0 : year % 4 == 0;
}
5.變參宏 函數可變參數
// 例如可以像如下幾種方式使用NSAssert這個宏
NSAssert(10 > 11, @"錯誤1:%@", @"err desc 1");
NSAssert(10 > 11, @"錯誤1:%@\n錯誤2:%@", @"err desc 1", @"err desc 2");
關於宏定義中的#和##的說明
#
有兩個作用:
1.將變量直接轉化為相應字面量的C語言字符串 如a=10 #a會轉化為"10"
2.連接兩個C字符串,使用如下
#define toString(a) #a // 轉為字符串
#define printSquare(x) printf("the square of " #x " is %d.\n",(x)*(x)) // 連接字符串
使用如下
printf("%s\n", toString(abc)); // abc
printSquare(3); // the square of 3 is 9.
##
的常見用處是連接,它會將在它之前的語句、表達式等和它之后的語句表達式等直接連接。這里有個特殊的規則,在逗號和__VA_ARGS__之間的雙井號,除了拼接前后文本之外,還有一個功能,那就是如果后方文本為空,那么它會將前面一個逗號吃掉。這個特性當且僅當上面說的條件成立時才會生效,因此可以說是特例。
// 用法1:代碼和表達式直接連接
#define combine(a, b) a##b
#define exp(a, b) (long)(a##e##b)
// 用法2:給表達式加前綴
#define addPrefixForVar(a) mk_##a
// 用法3,用於連接printf-like方法的format語句和可變參數
#define format(format, ...) [NSString stringWithFormat:format, ##__VA_ARGS__]
使用示例如下:
NSLog(@"%zd", combine(10, 556)); // 10556
NSLog(@"%f", combine(10, .556)); // 10.556000
NSLog(@"%tu", exp(2, 3)); // 2000
// int x = 10;
int mk_x = 30;
NSLog(@"%d", addPrefixForVar(x)); // 30
NSLog(@"%@", format(@"%@_%@", @"good", @"morning")); // good_morning
NSLog(@"%@", format(@"hello")); // hello
對於使用##
連接可變參數的用法,如果不加##
會導致編譯無法通過,這是因為: 這個##在逗號和__VA_ARGS__之間,后面的值不存在的時候 會忽略## 前面的,
也就是說:
當有## 調用format(format) 替換為 [NSString stringWithFormat:format]
當沒有## 調用format(format) 替換為 [NSString stringWithFormat:format ,]
還有一點要注意的是:#
和##
只能用在宏定義中,而不能使用在函數或者方法中。
Apple使用宏的一些習慣
1.類的聲明除了引用的其他頭文件 用下面一對宏標記
#define NS_ASSUME_NONNULL_BEGIN _Pragma("clang assume_nonnull begin")
#define NS_ASSUME_NONNULL_END _Pragma("clang assume_nonnull end")
也可以使用下面的兩句
#pragma clang assume_nonull begin
#pragma clang assume_nonull end
告訴clang編譯器這兩者之間內容非空
2. 在類聲明前,方法聲明后都有可用性的標記宏
例如
NS_CLASS_AVAILABLE // 類之前
NS_AVAILABLE(_mac, _ios) // 方法之后
NS_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, ...) // 方法之后
OBJC_SWIFT_UNAVAILABLE("use 'xxx' instead") // 針對swift特殊說明取代它的類和方法
不同功能的類之前的可用性標記不同,例如NSAutoreleasePool之前
NS_AUTOMATED_REFCOUNT_UNAVAILABLE
@interface NSAutoreleasePool : NSObject{ ...
對於這些標記究竟干了些什么,請看本文第三部分__attribute__。
3.嵌套的宏(經常成對使用)
#define NS_DURING @try {
#define NS_HANDLER } @catch (NSException *localException) {
#define NS_ENDHANDLER }
#define NS_VALUERETURN(v,t) return (v)
#define NS_VOIDRETURN return
#if __clang__
#define __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wformat-extra-args\"")
#define __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS _Pragma("clang diagnostic pop")
#else
#define __PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS
#define __PRAGMA_POP_NO_EXTRA_ARG_WARNINGS
#endif
4.經常使用宏來配合條件編譯 (為了適配當前的硬件環境,系統環境、語言環境、編譯環境)
#if !defined(NS_BLOCKS_AVAILABLE)
#if __BLOCKS__ && (MAC_OS_X_VERSION_10_6 <= MAC_OS_X_VERSION_MAX_ALLOWED || __IPHONE_4_0 <= __IPHONE_OS_VERSION_MAX_ALLOWED)
#define NS_BLOCKS_AVAILABLE 1
#else
#define NS_BLOCKS_AVAILABLE 0
#endif
#endif
#if !defined(NS_NONATOMIC_IOSONLY)
#if TARGET_OS_IPHONE
#define NS_NONATOMIC_IOSONLY nonatomic
#else
#if __has_feature(objc_property_explicit_atomic)
#define NS_NONATOMIC_IOSONLY atomic
#else
#define NS_NONATOMIC_IOSONLY
#endif
#endif
#endif
#if defined(__cplusplus)
#define FOUNDATION_EXTERN extern "C"
#else
#define FOUNDATION_EXTERN extern
#endif
5. 由__attribute__定義的宏處處可見。
(本文在第三部分詳細介紹__attribute__.).
例如下面這些常見的宏都屬於這種:
// NSLog 方法后面使用的宏
#define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A)))
// 這些都是在<Foundation/NSObjCRuntime.h>中定義的宏
#define NS_RETURNS_RETAINED __attribute__((ns_returns_retained))
#define NS_RETURNS_NOT_RETAINED __attribute__((ns_returns_not_retained))
#define NS_RETURNS_INNER_POINTER __attribute__((objc_returns_inner_pointer))
#define NS_AUTOMATED_REFCOUNT_UNAVAILABLE __attribute__((unavailable("not available in automatic reference counting mode")))
#define NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE __attribute__((objc_arc_weak_reference_unavailable))
#define NS_REQUIRES_PROPERTY_DEFINITIONS __attribute__((objc_requires_property_definitions))
#define NS_REPLACES_RECEIVER __attribute__((ns_consumes_self)) NS_RETURNS_RETAINED
#define NS_RELEASES_ARGUMENT __attribute__((ns_consumed))
#define NS_VALID_UNTIL_END_OF_SCOPE __attribute__((objc_precise_lifetime))
#define NS_ROOT_CLASS __attribute__((objc_root_class))
#define NS_REQUIRES_SUPER __attribute__((objc_requires_super))
#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
#define NS_PROTOCOL_REQUIRES_EXPLICIT_IMPLEMENTATION __attribute__((objc_protocol_requires_explicit_implementation))
#define NS_CLASS_AVAILABLE(_mac, _ios) __attribute__((visibility("default"))) NS_AVAILABLE(_mac, _ios)
#define NS_CLASS_DEPRECATED(_mac, _macDep, _ios, _iosDep, ...) __attribute__((visibility("default"))) NS_DEPRECATED(_mac, _macDep, _ios, _iosDep, __VA_ARGS__)
#define NS_SWIFT_NOTHROW __attribute__((swift_error(none)))
#define NS_INLINE static __inline__ __attribute__((always_inline))
#define NS_REQUIRES_NIL_TERMINATION __attribute__((sentinel(0,1)))
而我們經常看到的這樣的幾個宏
#define NS_AVAILABLE(_mac, _ios) CF_AVAILABLE(_mac, _ios)
#define NS_AVAILABLE_MAC(_mac) CF_AVAILABLE_MAC(_mac)
#define NS_AVAILABLE_IOS(_ios) CF_AVAILABLE_IOS(_ios)
這三個其實被定義在了<CoreFoundation/CFAvailability.h>中
#define CF_AVAILABLE(_mac, _ios) __attribute__((availability(macosx,introduced=_mac)))
#define CF_AVAILABLE_MAC(_mac) __attribute__((availability(macosx,introduced=_mac)))
#define CF_AVAILABLE_IOS(_ios) __attribute__((availability(macosx,unavailable)))
總之你會發現只要是不具備表達式或者代碼塊功能的宏,絕大多數都是由__attribute__定義的,那么__attribute__到底是什么呢,請繼續:
__attribute__
GNU C 的一大特色就是__attribute__機制。__attribute__可以設置函數屬性(Function Attribute )、變量屬性(Variable Attribute )和類型屬性(Type Attribute )。
__attribute__的書寫方式是:__attribute__后面會緊跟一對原括弧,括弧里面是相應的__attribute__參數,格式如:
__attribute__((attribute-list))
其位置約束為:放於聲明的尾部“;” 之前
。
那么現在的問題就是什么情況下使用__attribute__,以及如何設置參數attribute-list,主要分為三種情況:
函數屬性(Function Attribute )、變量屬性(Variable Attribute )和類型屬性(Type Attribute )。
1.函數屬性(Function Attribute )
函數屬性可以幫助開發者把一些特性添加到函數聲明中,從而可以使編譯器在錯誤檢查方面的功能更強大。__attribute__機制也很容易同非GNU應用程序做到兼容之功效。
__attribute__((format))
例如NSLog聲明中使用到的宏:
#define NS_FORMAT_FUNCTION(F,A) __attribute__((format(__NSString__, F, A)))
__attribute__((format(__NSString__, F, A)))
該__attribute__屬性可以給被聲明的函數加上類似printf或者scanf的特征,它可以使編譯器檢查函數聲明和函數實際調用參數之間的格式化字符串是否匹配。該功能十分有用,尤其是處理一些很難發現的bug。對於format參數的使用如下
format (archetype, string-index, first-to-check)
第一參數需要傳遞“archetype”指定是哪種風格;“string-index”指定傳入函數的第幾個參數是格式化字符串;“first-to-check”指定第一個可變參數所在的索引。
如NSLog對這個宏的使用如下:
FOUNDATION_EXPORT void NSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
那么它如何進行語法檢查,看下面的例子:
extern void myPrint(const char *format,...)__attribute__((format(printf,1,2)));
void test() {
myPrint("i=%d\n",6);
myPrint("i=%s\n",6);
myPrint("i=%s\n","abc");
myPrint("%s,%d,%d\n",1,2);
}
使用clang命令編譯一下:
clang -c main.m
結果會出現下面的警告
main.m:70:22: warning: format specifies type 'char *' but the argument has type 'int' [-Wformat]
myPrint("i=%s\n",6);
~~ ^
%d
main.m:72:26: warning: format specifies type 'char *' but the argument has type 'int' [-Wformat]
myPrint("%s,%d,%d\n",1,2);
~~ ^
%d
main.m:72:21: warning: more '%' conversions than data arguments [-Wformat]
myPrint("%s,%d,%d\n",1,2);
~^
3 warnings generated.
如果將
__attribute__((format(printf,1,2)))直接去掉,再次編譯,則不會有任何警告。
__attribute__((noreturn))
該屬性通知編譯器函數從不返回值。當遇到類似函數還未運行到return語句就需要退出來的情況,該屬性可以避免出現錯誤信息。C庫函數中的abort()和exit()的聲明格式就采用了這種格式。使用如下:
void fatal () __attribute__ ((noreturn));
void fatal (/* ... */) {
/* ... */
/* Print error message. */
/* ... */
exit (1);
}
__attribute__((constructor/destructor))
若函數被設定為constructor屬性,則該函數會在main()函數執行之前被自動的執行。類似的,若函數被設定為destructor屬性,則該 函數會在main()函數執行之后或者exit()被調用后被自動的執行。擁有此類屬性的函數經常隱式的用在程序的初始化數據方面。
例如:
static __attribute__((constructor)) void before() {
printf("Hello");
}
static __attribute__((destructor)) void after() {
printf(" World!\n");
}
int main(int argc, const char * argv[]) {
printf("0000");
return 0;
}
程序輸出結果是:
Hello0000 World!
如果多個函數使用了這個屬性,可以為它們設置優先級來決定執行的順序:
__attribute__((constructor(PRIORITY)))
__attribute__((destructor(PRIORITY)))
如:
static __attribute__((constructor(101))) void before1() {
printf("before1\n");
}
static __attribute__((constructor(102))) void before2() {
printf("before2\n");
}
static __attribute__((destructor(201))) void after1() {
printf("after1\n");
}
static __attribute__((destructor(202))) void after2() {
printf("after2\n");
}
int main(int argc, const char * argv[]) {
printf("0000\n");
return 0;
}
輸出的結果是:
before1
before2
0000
after2
after1
從輸出的信息看,前處理都是按照優先級先后執行的,而后處理則是相反的.
需要注意的是:優先級是有范圍的。0-100(包括100),是內部保留的,所以在編碼的時候需要注意.
另外一個注意的點是,上面的函數沒有聲明而是直接實現這樣__attribute__就需要放到前面,應該多使用函數聲明和實現分離的方法:
static void before1() __attribute__((constructor(1)));
static void before1() {
printf("before1\n");
}
其他的函數屬性(Function Attribute )還有: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等等,可以參考 GNU C Function Attribute查看它們的具體使用方法。
如何同時使用多個屬性:
可以在同一個函數聲明里使用多個__attribute__,並且實際應用中這種情況是十分常見的。只需要把它們用','隔開就可以,如:
__attribute__((noreturn, format(printf, 1, 2)))
2.類型屬性 (Type Attributes)
__attribute__ aligned
該屬性設定一個指定大小的對齊格式(以字節 為單位),例如:
struct S {
short f[3];
} __attribute__ ((aligned (8)));
typedef int more_aligned_int __attribute__ ((aligned (8)));
該聲明將強制編譯器確保(盡它所能)變量類型為struct S 或者more_aligned_int的變量在分配空間時采用至少8字節對齊方式。
如上所述,你可以手動指定對齊的格式,同樣,你也可以使用默認的對齊方式。如果aligned 后面不緊跟一個指定的數字值,那么編譯器將依據你的目標機器情況使用最大最有益的對齊方式。例如:
struct S {
short f[3];
} __attribute__ ((aligned));
這里,如果sizeof(short)的大小為2(byte),那么,S的大小就為6。取一個2的次方值,使得該值大於等於6,則該值為8,所以編譯器將設置S類型的對齊方式為8字節。
aligned 屬性使被設置的對象占用更多的空間,相反的,使用packed 可以減小對象占用的空間。
需要注意的是,attribute屬性的效力與你的連接器也有關,如果你的連接器最大只支持16字節對齊,那么你此時定義32 字節對齊也是無濟於事的。
__attribute__ packed
使用該屬性對struct或者union類型進行定義,設定其類型的每一個變量的內存約束。當用在enum類型定義時,暗示了應該使用最小完整的類型(it indicates that the smallest integral type should be used)。
下面的例子中,packed_struct 類型的變量數組中的值將會緊緊的靠在一起,但內部的成員變量s不會被“pack” ,如果希望內部的成員變量也被packed 的話,unpacked-struct也需要使用packed進行相應的約束。
struct my_unpacked_struct {
char c;
int i;
};
struct my_packed_struct {
char c;
int i;
struct my_unpacked_struct s;
}__attribute__ ((__packed__));
下面的例子中使用這個屬性定義了一些結構體及其變量,並給出了輸出結果和對結果的分析。
程序代碼為:
struct p {
int a;
char b;
short c;
}__attribute__((aligned(4))) pp;
struct m {
char a;
int b;
short c;
}__attribute__((aligned(4))) mm;
struct o {
int a;
char b;
short c;
}oo;
struct x {
int a;
char b;
struct p px;
short c;
}__attribute__((aligned(8))) xx;
int main() {
printf("sizeof(int)=%d,sizeof(short)=%d.sizeof(char)=%d\n",sizeof(int),sizeof(short),sizeof(char));
printf("pp=%d,mm=%d \n", sizeof(pp),sizeof(mm));
printf("oo=%d,xx=%d \n", sizeof(oo),sizeof(xx));
return 0;
}
輸出結果為:
sizeof(int)=4,sizeof(short)=2.sizeof(char)=1
pp=8,mm=12
oo=8,xx=24
分析:
1.sizeof(pp):
sizeof(a)+sizeof(b)+sizeof(c)=4+1+1=6<8 所以sizeof(pp)=8
2.sizeof(mm):
sizeof(a)+sizeof(b)+sizeof(c)=1+4+2=7
但是a后面需要用3個字節填充,但是b是4個字節,所以a占用4字節,b占用4 個字節,而c又要占用4個字節。所以sizeof(mm)=12
3.sizeof(oo):
sizeof(a)+sizeof(b)+sizeof(c)=4+1+2=7
因為默認是以4字節對齊,所以sizeof(oo)=8
4.sizeof(xx):
sizeof(a)+ sizeof(b)=4+1=5
sizeof(pp)=8; 即xx是采用8字節對齊的,所以要在a,b后面添3個空余字節,然后才能存儲px,
4+1+(3)+8+1=17
因為xx采用的對齊是8字節對齊,所以xx的大小必定是8的整數倍,即xx的大小是一個比17大又是8的倍數的一個最小值,17<24,所以sizeof(xx)=24.
其他的函數屬性(Function Attribute )還有:aligned, packed, transparent_union, unused, deprecated and may_alias等等,可以參考GNU C Function Attribute查看它們的具體使用方法。
3.變量屬性 (Variable Attribute)
變量屬性最常用的是aligned和packed(和上面介紹的用法相同),其他變量屬性的用法請參考GNU C Variable Attribute。
4.Clang特有的__attribute__
如同GCC編譯器, Clang也支持 __attribute__, 而且對它進行的一些擴展.
如果要檢查指定屬性的可用性,你可以使用__has_attribute指令。如:
#if defined(__has_feature)
#if __has_attribute(availability)
// 如果`availability`屬性可以使用,則....
#endif
#endif
下面介紹一些clang特有的attribute
availability
該屬性描述了方法關於操作系統版本的適用性, 使用如下:
(void) __attribute__((availability(macosx,introduced=10.4,deprecated=10.6,obsoleted=10.7)));
他表示被修飾的方法在首次出現在 OS X Tiger(10.4),在OS X Snow Leopard(10.6)中廢棄,在 OS X Lion(10.7)移除。
clang可以利用這些信息決定什么時候使用它使安全的。比如:clang在OS X Leopard()編譯代碼,調用這個方法是成功的,如果在OS X Snow Leopard編譯代碼,調用成功但是會發出警告指明這個方法已經廢棄,如果是在OS X Lion,調用會失敗,因為它不再可用。
availability屬性是一個以逗號為分隔的參數列表,以平台的名稱開始,包含一些放在附加信息里的一些里程碑式的聲明。
introduced:第一次出現的版本。
deprecated:聲明要廢棄的版本,意味着用戶要遷移為其他API
obsoleted:聲明移除的版本,意味着完全移除,再也不能使用它
unavailable:在這些平台不可用
message:一些關於廢棄和移除的額外信息,clang發出警告的時候會提供這些信息,對用戶使用替代的API非常有用。
這個屬性支持的平台:
ios,macosx。
overloadable
clang 在C語言中實現了對C++函數重載的支持,使用overloadable屬性可以實現。例如:
#include <math.h>
float __attribute__((overloadable)) tgsin(float x) { return sinf(x); }
double __attribute__((overloadable)) tgsin(double x) { return sin(x); }
long double __attribute__((overloadable)) tgsin(long double x) { return sinl(x); }
要注意的是overloadable僅僅對函數有效, 你可以重載方法,不過范圍局限於返回值和參數是常規類型的如:id和void *。