本文目錄 1. iOS巨集的經典用法 2. Apple的習慣3. \_\_attribute\_\_ iOS巨集的經典用法 1.常量巨集、表達式巨集 2.帶參數的巨集 3.函數巨集(是一個沒有返回值的代碼塊,通常當做一行語句使用) 4.內聯函數 (一般有返回值) 5.變參巨集 函數可變參數 關於巨集定義...
本文目錄
- 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) 一般是沒有返回值的,僅僅是代碼塊而已
#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.
##
的常見用處是連接,它會將在它之前的語句、表達式等和它之後的語句表達式等直接連接。
// 用法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
對於使用##
連接可變參數的用法,如果不加##
會導致編譯無法通過,這是因為: ##後面的值不存在的時候 會忽略## 前面的,
也就是說:
當有## 調用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的文檔查看它們的具體使用方法。
如何同時使用多個屬性:
可以在同一個函數聲明裡使用多個__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的文檔查看它們的具體使用方法。
3.變數屬性 (Variable Attribute)
變數屬性最常用的是aligned和packed其他的用法請參考 GNU C的文檔。
4.Clang特有的__attribute__
如同GCC編譯器, Clang也支持 __attribute__, 而且對它進行的一些擴展.
如果要檢查指定屬性的可用性,你可以使用__has_attribute指令。
下麵介紹一些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 *。