iOS宏的經(jīng)典用法
Apple的習(xí)慣
attribute
iOS宏的經(jīng)典用法
1.常量宏、表達(dá)式宏
define kTabBarH (49.0)
define kScreenH [UIScreen mainScreen].bounds.size.height
define isScreenWidthEqual320 (fabs([UIScreen mainScreen].bounds.size.width - 320) < DBL_EPSILON)
2.帶參數(shù)的宏
// 例子1:同樣是一個(gè)表達(dá)式#define isNilOrNull(obj) (obj == nil || [obj isEqual:[NSNull null]])// 例子2:也可以沒有參數(shù)值 使用的時(shí)候要加上"()",是在使用的時(shí)候單獨(dú)成為一行語句的宏#define MKAssertMainThread() NSAssert([NSThread isMainThread], @"This method must be called on the main thread")
3.函數(shù)宏(是一個(gè)沒有返回值的代碼塊,通常當(dāng)做一行語句使用)
// 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.內(nèi)聯(lián)函數(shù) (一般有返回值)
CG_INLINE CGRectCGRectMake(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.變參宏 函數(shù)可變參數(shù)
// 例如可以像如下幾種方式使用NSAssert這個(gè)宏NSAssert(10 > 11, @"錯(cuò)誤1:%@", @"err desc 1");NSAssert(10 > 11, @"錯(cuò)誤1:%@/n錯(cuò)誤2:%@", @"err desc 1", @"err desc 2");
關(guān)于宏定義中的#和##的說明#
有兩個(gè)作用:1.將變量直接轉(zhuǎn)化為相應(yīng)字面量的C語言字符串 如a=10 #a會(huì)轉(zhuǎn)化為"a"2.連接兩個(gè)C字符串,使用如下
define toString(a) #a // 轉(zhuǎn)為字符串#define printSquare(x) printf("the square of " #x " is %d./n",(x)*(x)) // 連接字符串
使用如下
printf("%s/n", toString(abc)); // abcprintSquare(3); // the square of 3 is 9.
的常見用處是連接,它會(huì)將在它之前的語句、表達(dá)式等和它之后的語句表達(dá)式等直接連接。
// 用法1:代碼和表達(dá)式直接連接#define combine(a, b) a##b #define exp(a, b) (long)(a##e##b) // 用法2:給表達(dá)式加前綴#define addPrefixForVar(a) mk_##a // 用法3,用于連接printf-like方法的format語句和可變參數(shù)#define format(format, ...) [NSString stringWithFormat:format, VA_ARGS]
使用示例如下:
NSLog(@"%zd", combine(10, 556)); // 10556NSLog(@"%f", combine(10, .556)); // 10.556000NSLog(@"%tu", exp(2, 3)); // 2000 // int x = 10;int mk_x = 30;NSLog(@"%d", addPrefixForVar(x)); // 30 NSLog(@"%@", format(@"%@_%@", @"good", @"morning"));NSLog(@"%@", format(@"hello"));
對于使用##
連接可變參數(shù)的用法,如果不加##
會(huì)導(dǎo)致編譯無法通過,這是因?yàn)椋?##后面的值不存在的時(shí)候 會(huì)忽略## 前面的,
也就是說:當(dāng)有## 調(diào)用format(format) 替換為 [NSString stringWithFormat:format]當(dāng)沒有## 調(diào)用format(format) 替換為 [NSString stringWithFormat:format ,]
還有一點(diǎn)要注意的是:#
和##
只能用在宏定義中,而不能使用在函數(shù)或者方法中。
Apple使用宏的一些習(xí)慣
1.類的聲明除了引用的其他頭文件 用下面一對宏標(biāo)記
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編譯器這兩者之間內(nèi)容非空
- 在類聲明前,方法聲明后都有可用性的標(biāo)記宏
例如
NS_CLASS_AVAILABLE // 類之前NS_AVAILABLE(_mac, _ios) // 方法之后NS_DEPRECATED(_macIntro, _macDep, _iosIntro, _iosDep, ...) // 方法之后OBJC_SWIFT_UNAVAILABLE("use 'xxx' instead") // 針對swift特殊說明取代它的類和方法
不同功能的類之前的可用性標(biāo)記不同,例如NSAutoreleasePool之前
NS_AUTOMATED_REFCOUNT_UNAVAILABLE@interface NSAutoreleasePool : NSObject{ ...
對于這些標(biāo)記究竟干了些什么,請看本文第三部分attribute。
3.嵌套的宏(經(jīng)常成對使用)
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.經(jīng)常使用條件宏 (為了適配當(dāng)前的硬件環(huán)境,系統(tǒng)環(huán)境、語言環(huán)境、編譯環(huán)境)
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
- 由attribute定義的宏處處可見。
(本文在第三部分詳細(xì)介紹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)))
而我們經(jīng)??吹降倪@樣的幾個(gè)宏
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)))
總之你會(huì)發(fā)現(xiàn)只要是不具備表達(dá)式或者代碼塊功能的宏,絕大多數(shù)都是由attribute定義的,那么attribute到底是什么呢,請繼續(xù):
attribute
GNU C 的一大特色就是attribute機(jī)制。attribute可以設(shè)置函數(shù)屬性(Function Attribute )、變量屬性(Variable Attribute )和類型屬性(Type Attribute )。attribute的書寫方式是:attribute后面會(huì)緊跟一對原括弧,括弧里面是相應(yīng)的attribute參數(shù),格式如:attribute((attribute-list))其位置約束為:放于聲明的尾部“;” 之前。那么現(xiàn)在的問題就是什么情況下使用attribute,以及如何設(shè)置參數(shù)attribute-list,主要分為三種情況:函數(shù)屬性(Function Attribute )、變量屬性(Variable Attribute )和類型屬性(Type Attribute )。
1.函數(shù)屬性(Function Attribute )
函數(shù)屬性可以幫助開發(fā)者把一些特性添加到函數(shù)聲明中,從而可以使編譯器在錯(cuò)誤檢查方面的功能更強(qiáng)大。attribute機(jī)制也很容易同非GNU應(yīng)用程序做到兼容之功效。
attribute((format))
例如NSLog聲明中使用到的宏:
define NS_FORMAT_FUNCTION(F,A) attribute((format(NSString, F, A)))
attribute((format(NSString, F, A)))
該attribute屬性可以給被聲明的函數(shù)加上類似printf或者scanf的特征,它可以使編譯器檢查函數(shù)聲明和函數(shù)實(shí)際調(diào)用參數(shù)之間的格式化字符串是否匹配。該功能十分有用,尤其是處理一些很難發(fā)現(xiàn)的bug。對于format參數(shù)的使用如下format (archetype, string-index, first-to-check)第一參數(shù)需要傳遞“archetype”指定是哪種風(fēng)格;“string-index”指定傳入函數(shù)的第幾個(gè)參數(shù)是格式化字符串;“first-to-check”指定第一個(gè)可變參數(shù)所在的索引。如NSLog對這個(gè)宏的使用如下:
FOUNDATION_EXPORT void NSLog(NSString *format, ...) NS_FORMAT_FUNCTION(1,2);
那么它如何進(jìn)行語法檢查,看下面的例子:
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
結(jié)果會(huì)出現(xiàn)下面的警告
main.m:70:22: warning: format specifies type 'char *' but the argument has type 'int' [-Wformat] myPrint("i=%s/n",6); ~~ ^ %dmain.m:72:26: warning: format specifies type 'char *' but the argument has type 'int' [-Wformat] myPrint("%s,%d,%d/n",1,2); ~~ ^ %dmain.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)))直接去掉,再次編譯,則不會(huì)有任何警告。
attribute((noreturn))
該屬性通知編譯器函數(shù)從不返回值。當(dāng)遇到類似函數(shù)還未運(yùn)行到return語句就需要退出來的情況,該屬性可以避免出現(xiàn)錯(cuò)誤信息。C庫函數(shù)中的abort()和exit()的聲明格式就采用了這種格式。使用如下:
void fatal () attribute ((noreturn)); void fatal (/* ... /) { / ... / / Print error message. / / ... */ exit (1);}
attribute((constructor/destructor))
若函數(shù)被設(shè)定為constructor屬性,則該函數(shù)會(huì)在main()函數(shù)執(zhí)行之前被自動(dòng)的執(zhí)行。類似的,若函數(shù)被設(shè)定為destructor屬性,則該 函數(shù)會(huì)在main()函數(shù)執(zhí)行之后或者exit()被調(diào)用后被自動(dòng)的執(zhí)行。擁有此類屬性的函數(shù)經(jīng)常隱式的用在程序的初始化數(shù)據(jù)方面。例如:
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;}
程序輸出結(jié)果是:
Hello0000 World!
如果多個(gè)函數(shù)使用了這個(gè)屬性,可以為它們設(shè)置優(yōu)先級(jí)來決定執(zhí)行的順序:
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;}
輸出的結(jié)果是:
before1before20000after2after1
從輸出的信息看,前處理都是按照優(yōu)先級(jí)先后執(zhí)行的,而后處理則是相反的.需要注意的是:優(yōu)先級(jí)是有范圍的。0-100(包括100),是內(nèi)部保留的,所以在編碼的時(shí)候需要注意.另外一個(gè)注意的點(diǎn)是,上面的函數(shù)沒有聲明而是直接實(shí)現(xiàn)這樣attribute就需要放到前面,應(yīng)該多使用函數(shù)聲明和實(shí)現(xiàn)分離的方法:
static void before1() attribute((constructor(1)));static void before1() { printf("before1/n");}
其他的函數(shù)屬性(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的文檔查看它們的具體使用方法。
如何同時(shí)使用多個(gè)屬性:可以在同一個(gè)函數(shù)聲明里使用多個(gè)attribute,并且實(shí)際應(yīng)用中這種情況是十分常見的。只需要把它們用','隔開就可以,如:
attribute((noreturn, format(printf, 1, 2)))
類型屬性 (Type Attributes)
attribute aligned
該屬性設(shè)定一個(gè)指定大小的對齊格式(以字節(jié) 為單位),例如:
struct S { short f[3];} attribute ((aligned (8)));typedef int more_aligned_int attribute ((aligned (8)));
該聲明將強(qiáng)制編譯器確保(盡它所能)變量類型為struct S 或者more_aligned_int的變量在分配空間時(shí)采用至少8字節(jié)對齊方式。如上所述,你可以手動(dòng)指定對齊的格式,同樣,你也可以使用默認(rèn)的對齊方式。如果aligned 后面不緊跟一個(gè)指定的數(shù)字值,那么編譯器將依據(jù)你的目標(biāo)機(jī)器情況使用最大最有益的對齊方式。例如:
struct S { short f[3];} attribute ((aligned));
這里,如果sizeof(short)的大小為2(byte),那么,S的大小就為6。取一個(gè)2的次方值,使得該值大于等于6,則該值為8,所以編譯器將設(shè)置S類型的對齊方式為8字節(jié)。aligned 屬性使被設(shè)置的對象占用更多的空間,相反的,使用packed 可以減小對象占用的空間。需要注意的是,attribute屬性的效力與你的連接器也有關(guān),如果你的連接器最大只支持16字節(jié)對齊,那么你此時(shí)定義32 字節(jié)對齊也是無濟(jì)于事的。
attribute packed
使用該屬性對struct或者union類型進(jìn)行定義,設(shè)定其類型的每一個(gè)變量的內(nèi)存約束。當(dāng)用在enum類型定義時(shí),暗示了應(yīng)該使用最小完整的類型(it indicates that the smallest integral type should be used)。
下面的例子中,packed_struct 類型的變量數(shù)組中的值將會(huì)緊緊的靠在一起,但內(nèi)部的成員變量s不會(huì)被“pack” ,如果希望內(nèi)部的成員變量也被packed 的話,unpacked-struct也需要使用packed進(jìn)行相應(yīng)的約束。
struct my_unpacked_struct { char c; int i;};struct my_packed_struct { char c; int i; struct my_unpacked_struct s;}attribute ((packed));
下面的例子中使用這個(gè)屬性定義了一些結(jié)構(gòu)體及其變量,并給出了輸出結(jié)果和對結(jié)果的分析。程序代碼為:
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;}
輸出結(jié)果為:
sizeof(int)=4,sizeof(short)=2.sizeof(char)=1pp=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個(gè)字節(jié)填充,但是b是4個(gè)字節(jié),所以a占用4字節(jié),b占用4 個(gè)字節(jié),而c又要占用4個(gè)字節(jié)。所以sizeof(mm)=12
3.sizeof(oo):sizeof(a)+sizeof(b)+sizeof(c)=4+1+2=7因?yàn)槟J(rèn)是以4字節(jié)對齊,所以sizeof(oo)=8
4.sizeof(xx):sizeof(a)+ sizeof(b)=4+1=5sizeof(pp)=8; 即xx是采用8字節(jié)對齊的,所以要在a,b后面添3個(gè)空余字節(jié),然后才能存儲(chǔ)px,4+1+(3)+8+1=17因?yàn)閤x采用的對齊是8字節(jié)對齊,所以xx的大小必定是8的整數(shù)倍,即xx的大小是一個(gè)比17大又是8的倍數(shù)的一個(gè)最小值,17<24,所以sizeof(xx)=24.
其他的函數(shù)屬性(Function Attribute )還有:aligned, packed, transparent_union, unused, deprecated and may_alias等等,可以參考GNU C的文檔查看它們的具體使用方法。
變量屬性 (Variable Attribute)
變量屬性最常用的是aligned和packed其他的用法請參考 GNU C的文檔。
Clang特有的attribute
如同GCC編譯器, Clang也支持 attribute, 而且對它進(jìn)行的一些擴(kuò)展.如果要檢查指定屬性的可用性,你可以使用__has_attribute指令。下面介紹一些clang特有的attribute
availability
該屬性描述了方法關(guān)于操作系統(tǒng)版本的適用性, 使用如下:
(void) attribute((availability(macosx,introduced=10.4,deprecated=10.6,obsoleted=10.7)));
他表示被修飾的方法在首次出現(xiàn)在 OS X Tiger(10.4),在OS X Snow Leopard(10.6)中廢棄,在 OS X Lion(10.7)移除。clang可以利用這些信息決定什么時(shí)候使用它使安全的。比如:clang在OS X Leopard()編譯代碼,調(diào)用這個(gè)方法是成功的,如果在OS X Snow Leopard編譯代碼,調(diào)用成功但是會(huì)發(fā)出警告指明這個(gè)方法已經(jīng)廢棄,如果是在OS X Lion,調(diào)用會(huì)失敗,因?yàn)樗辉倏捎?。availability屬性是一個(gè)以逗號(hào)為分隔的參數(shù)列表,以平臺(tái)的名稱開始,包含一些放在附加信息里的一些里程碑式的聲明。introduced:第一次出現(xiàn)的版本。deprecated:聲明要廢棄的版本,意味著用戶要遷移為其他APIobsoleted:聲明移除的版本,意味著完全移除,再也不能使用它unavailable:在這些平臺(tái)不可用message:一些關(guān)于廢棄和移除的額外信息,clang發(fā)出警告的時(shí)候會(huì)提供這些信息,對用戶使用替代的API非常有用。這個(gè)屬性支持的平臺(tái):ios,macosx。
overloadable
clang 在C語言中實(shí)現(xiàn)了對C++函數(shù)重載的支持,使用overloadable屬性可以實(shí)現(xiàn)。例如:
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僅僅對函數(shù)有效, 你可以重載方法,不過范圍局限于返回值和參數(shù)是常規(guī)類型的如:id和void *。