提起宏定義相信所有人都知道是干什么用的撩荣,在項(xiàng)目中也經(jīng)常使用餐曹,通常情況下我們用宏定義也就是定義一些”魔鬼常量”和短函數(shù)敌厘。
#define M_PI 3.14159265358979323846264338327950288
#define MAX(a, b) ((a) > (b) ? (a) : (b))
如果僅僅會(huì)使用類似上面的宏定義俱两,那么其實(shí)對(duì)于宏定義的掌握只能算是剛剛?cè)腴T宪彩,作為一個(gè)資深iOS開發(fā)者,非常有必要深入研究一下宏定義的用法及其背后的原理
宏的定義
任何版本的翻譯都無法滿足宏定義的描述衍腥,這里直接摘抄GCC網(wǎng)站對(duì)于宏的定義描述婆咸。
A macro is a fragment of code which has been given a name. Whenever the name is used, it is replaced by the contents of the macro. There are two kinds of macros. They differ mostly in what they look like when they are used. Object-like macros resemble data objects when used, function-like macros resemble function calls.
You may define any valid identifier as a macro, even if it is a C keyword. The preprocessor does not know anything about keywords. This can be useful if you wish to hide a keyword such as const from an older compiler that does not understand it. However, the preprocessor operator defined (see Defined) can never be defined as a macro, and C++’s named operators (see C++ Named Operators) cannot be macros when you are compiling C++.
宏的類型及用法
Object-like 宏
1尚骄、在宏定義時(shí)侵续,通常宏的名稱都是用大寫字母表示,如果要換行就在行末使用 \
斷行鹉动。
#define SUM(a, b) (a + b)
#define NSLog(format, ...) NSLog((@"[文件名:%s]" "[函數(shù)名:%s]" "[行號(hào):%d]" format), \
__FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__);
2宏邮、在調(diào)用宏時(shí)蜜氨,預(yù)處理器在替換宏的內(nèi)容時(shí)會(huì)繼續(xù)檢查宏的內(nèi)容本身是否也是宏定義,如果是埋哟,會(huì)繼續(xù)替換宏定義的內(nèi)容赤赊,直到全部展開煞赢。
#define A(a1, a2) (a1 + a2)
#define B(b1, b2) (b1 + b2)
#define SUM(a, b) (a + b)
SUM(A(1, 2), B(3, 4)) => SUM((1 + 2), (3 + 4)) => (1 + 2) + (3 + 4) => 10
3耕驰、宏定義以最后有效的定義為準(zhǔn)朦肘。
#define SIZE (1024)
#define MAX_SIZE SIZE
#undef SIZE // 取消宏定義
#define SIZE (2048)
最終 SIZE
與 MAX_SIZE
的值都為 2048双饥。
個(gè)人建議不要隨意使用 #undef
咏花。
Function-like 宏
function-like宏后面可以加“()”,但是必須要緊隨宏名稱苍匆,否則就變成了object-like宏浸踩。
1统求、在“()”里可以添加參數(shù),以“,”分隔
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define SUM(a, b) (a + b)
2另假、如果有字符串內(nèi)容怕犁,即使與參數(shù)名稱相同也不會(huì)被替換
#define func(x) x, @"x"
NSLog(@"%@,%@", func(@"abc")); // 輸出結(jié)果為 abc,x
3因苹、使用 “#” 預(yù)處理操作符來實(shí)現(xiàn)將宏中的參數(shù)轉(zhuǎn)化為字符(串)扶檐,這個(gè)操作會(huì)將參數(shù)中的所有字符都實(shí)現(xiàn)字符(串)化,包括引號(hào)智蝠,如果參數(shù)中間有很多空格杈湾,字符(串)化之后將會(huì)只用一個(gè)空格代替攘须。
#define XSTR(s) STR(s)
#define STR(s) #s
#define FOO 4
NSLog(@"%s", STR(FOO)); // 輸出結(jié)果為 FOO
NSLog(@"%s", XSTR(FOO)); // 輸出結(jié)果為 4
出現(xiàn)上面的結(jié)果是因?yàn)樵谑褂?STR(s)
時(shí)于宙, s
是被字符(串)化的捞魁,所以宏沒有擴(kuò)展開;而使用 XSTR(s)
時(shí) s
作為一個(gè)參數(shù)奉件,因此先把宏完全擴(kuò)展然后再放進(jìn)參數(shù)县貌。
4窃这、使用 “##” 操作符可以實(shí)現(xiàn)宏中token的連接
#define VIEW(prefix/*前綴*/, suffix/*后綴*/) prefix##View##suffix
VIEW(UI,) // 等價(jià)于 UIView
VIEW(UI, Controller) // 等價(jià)于 UIViewController
示例所示表示把prefix和suffix對(duì)應(yīng)的內(nèi)容與View連接起來,當(dāng)然連接后的字符(串)必須是有意義的祟敛,否則會(huì)出現(xiàn)錯(cuò)誤或警告;但不能將 “##” 放在宏的最后埠巨,否則會(huì)出現(xiàn)錯(cuò)誤辣垒。
預(yù)處理器會(huì)將注釋轉(zhuǎn)化成空格勋桶,因此在宏中間侥猬,參數(shù)中間加入注釋都是可以的退唠。只能加入 /**/
這種注釋而不能加入 //
這種瞧预。
“##”的特殊用法:
#define NSLog(args, ...) NSLog(args, ##__VA_ARGS__);
NSLog(@"abc") // 輸出結(jié)果為 abc
NSLog(@"123, "@"abc") // 輸出結(jié)果為 123, abc
將 “##” 放在 “,” 和參數(shù)之間,那么如果參數(shù)留空的話扔茅,那么 “##” 前面的 “,” 就會(huì)刪掉,從而防止編譯錯(cuò)誤惊楼。
5秸讹、可變參數(shù)宏
使用標(biāo)識(shí)符 __VA_ARGS__
來表示多個(gè)參數(shù)璃诀,在宏的定義中則使用 (...)
表示劣欢。
#define NSLog(...) NSLog(__VA_ARGS__);
宏的示例詳細(xì)解析
看到這里相信您已經(jīng)對(duì)宏有更深層次的認(rèn)識(shí)了,但是我不得不說上面的一些示例只適應(yīng)于一般情況下价脾,在某些特殊情況下還是會(huì)出錯(cuò)的笛匙。
1妹孙、MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
如上求最大值的宏在下面的例子中就會(huì)出現(xiàn)意想不到的結(jié)果
NSInteger a = 1;
NSInteger b = 2;
NSInteger c = MAX(a, b++);
NSLog(@"a = %d, b = %d, c = %d", a, b, c); // 輸出結(jié)果為 a = 1, b = 4, c = 3
我們期望的輸出結(jié)果為 a = 1, b = 3, c = 2蠢正,但是實(shí)際輸出結(jié)果為 a = 1, b = 4, c = 3,為什么呢蜘拉?讓我們一步一步分析看旭旭,
表達(dá)式 c=MAX(a,b++);
展開為 c=((a)>(b++)?(a):(b++))
葱跋,
我們把值替換上看一下娱俺,在比較 1
和 b++
的時(shí)候荠卷, b++
的值為 2
然后 b
自增 1
油宜,此時(shí)條件不成立且 b
的值為 3
,然后再執(zhí)行 b++
疼燥,得到 c
的值為 3
, b
自增 1
得到結(jié)果為 4
蚁堤。我們本以為 b++
會(huì)執(zhí)行一次醉者,但是由于宏展開導(dǎo)致了 b++
被多次執(zhí)行了。
那么對(duì)于這種情況是不是就沒有徹底的解決方案了呢?答案是否定的撬即,系統(tǒng)有更優(yōu)雅的解決方案立磁。
#define MAX(A,B) ({ __typeof__(A) __a = (A); \
__typeof__(B) __b = (B); \
__a < __b ? __b : __a; })
這里用到GNU C的賦值擴(kuò)展,形式為 ({...})
息罗,這種形式的語句在順序執(zhí)行之后,會(huì)將最后一次執(zhí)行的結(jié)果返回才沧。
系統(tǒng)的 MAX(A,B)
定義了三個(gè)語句迈喉,分別以入?yún)⒌念愋吐暶鲀蓚€(gè)變量 __a
和 __b
,并用入?yún)槠滟x值温圆,接下來簡(jiǎn)單比較兩個(gè)值中最大的那個(gè)并把結(jié)果返回挨摸。
這樣的 MAX(A,B)
看上去很完美了,但是還是存在一定缺陷的岁歉,不信您寫成如下的代碼試試得运,結(jié)果留給讀者自行嘗試一下。
NSInteger __a = 1;
NSInteger __b = 2;
NSInteger __c = MAX(__a, __b++);
NSLog(@"__a = %d, __b = %d, __c = %d", __a, __b, __c);
Apple的大牛工程師肯定不會(huì)到此為止锅移,他們?cè)贑lang中徹底解決了這個(gè)問題熔掺,源代碼摘抄如下:
#define __NSX_PASTE__(A,B) A##B
#define __NSMAX_IMPL__(A,B,L) ({ __typeof__(A) __NSX_PASTE__(__a,L) = (A); \
__typeof__(B) __NSX_PASTE__(__b,L) = (B); \ (__NSX_PASTE__(__a,L) < __NSX_PASTE__(__b,L)) ? __NSX_PASTE__(__b,L) : __NSX_PASTE__(__a,L); })
#define MAX(A,B) __NSMAX_IMPL__(A,B,__COUNTER__)
MAX(A,B)
一共有三個(gè)宏來實(shí)現(xiàn)
第一個(gè) __NSX_PASTE__(A,B)
宏的作用是把兩個(gè)參數(shù)連接起來;
第二個(gè)宏才是實(shí)現(xiàn)的主體非剃,這個(gè)宏也是定義了三個(gè)語句置逻,分別以前兩個(gè)入?yún)⒌念愋吐暶鲀蓚€(gè)變量 __aL
和 __bL
(其中L是可變的,這個(gè)在第三個(gè)語句的時(shí)候再講)并用入?yún)槠滟x值备绽,接下來簡(jiǎn)單比較兩個(gè)值中最大的那個(gè)并把結(jié)果返回券坞;整個(gè)過程與上面 MAX(A,B)
的實(shí)現(xiàn)一模一樣,關(guān)鍵的地方就在于第三個(gè)語句的 __COUNTER__
參數(shù)肺素, __COUNTER__
是一個(gè)預(yù)定義的宏恨锚,編譯過程中將從0開始計(jì)數(shù),每被調(diào)用一次就加1(類似于數(shù)據(jù)庫的主鍵)倍靡,具有唯一性猴伶,用在這里是來定義獨(dú)立的變量名稱,那么第二條語句中的 __NSX_PASTE__(__a,L)(或__NSX_PASTE__(__a,L))
就會(huì)變成 __a(或__b)
加一個(gè)數(shù)字后綴來表示一個(gè)變量名字塌西,這樣就極大的避免了因變量名字相同而導(dǎo)致的問題他挎,但是如果您一定要把變量定義為__a888,那么出了問題您自己負(fù)責(zé)吧雨让。
2、NSAssert
NSAssert(self != nil, @"self is nil");
斷言是我們?cè)贒ebug調(diào)試中經(jīng)常使用的一個(gè)宏忿等,摘抄源碼來分析一下其中的技術(shù)點(diǎn)栖忠。
#define NSAssert(condition, desc, ...) \
do { \
__PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
if (__builtin_expect(!(condition), 0)) { \
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)
__PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS
__PRAGMA_POP_NO_EXTRA_ARG_WARNINGS
這兩個(gè)宏是用來屏蔽warnning的,不用過多解釋,我們?cè)陧?xiàng)目中也可以拿來使用的庵寞。
__builtin_expect
這個(gè)函數(shù)是GCC在2.96版本引入的狸相,函數(shù)聲明是 long__builtin_expect(longexp,longc);
,我們期望 exp 表達(dá)式的值等于常量 c捐川,如果 c 的值為0(即期望的函數(shù)返回值), 那么 執(zhí)行 if 分支的的可能性小, 否則執(zhí)行 else 分支的可能性小(函數(shù)的返回值等于第一個(gè)參數(shù) exp)脓鹃。
if判斷里的代碼就比較簡(jiǎn)單了;這里重點(diǎn)要說明的一點(diǎn)就是 do{}while()
的使用古沥,這里用的是while(0)瘸右,這樣的話貌似do{}里的內(nèi)容只是被執(zhí)行了一次,那這又有什么意義呢岩齿?相信自有它存在的道理太颤。
我們把上面的NSAssert展開來看其中的細(xì)節(jié)問題
do {
__PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS
if (__builtin_expect(!(self != nil), 0)) {
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:(@"self is nil")];
}
__PRAGMA_POP_NO_EXTRA_ARG_WARNINGS
} while(0);
如果您夠細(xì)心觀察,那么相信您已經(jīng)發(fā)現(xiàn)最后面的 ;
分號(hào)了盹沈,在NSAssert的宏定義中這個(gè)分號(hào)是沒有的龄章,而在宏展開后這里多了一個(gè)分號(hào),其實(shí)這個(gè)分號(hào)就是 NSAssert(self!=nil,@"self is nil");
這里的分號(hào)乞封, 那么這個(gè)分號(hào)就被 do{}while()
給吃掉了做裙,如果我們?cè)陧?xiàng)目中使用 do{}while()
后面不加分號(hào)還編譯不過呢。
這種吃掉 ;
分號(hào)的方式被大量宏代碼塊所使用肃晚,而且這種使用 while(0)
的好處方式在編譯時(shí)锚贱,編譯器會(huì)幫做好優(yōu)化操作,最終編譯的結(jié)果不會(huì)因?yàn)檫@個(gè) do{}while()
而導(dǎo)致運(yùn)行效率低下陷揪。
3惋鸥、ReactiveCocoa
如果 ReactiveCocoa 沒有那么多有用的宏的話,相信大多數(shù)人都不會(huì)選擇拿來使用了悍缠。接下來讓我們一一分析幾個(gè)常用的宏卦绣。
weakify(...)
strongify(...)
這兩個(gè)宏在代碼中總是配對(duì)出現(xiàn)
@weakify(self)
[RACObserve(self.model, name) subscribeNext:^(NSString *name) {
@strongify(self)
if(self)
{
self.nameLabel.text = name;
}
}];
為什么要配對(duì)出現(xiàn)這個(gè)是大家肯定都知道的,但是對(duì)于這個(gè)宏的展開您又了解多少呢飞蚓?使用時(shí)前面需要添加一個(gè) @
符號(hào)滤港,這又是為什么呢?接下來讓我們把宏展開來看看它的真面目趴拧。
下面我們把 @weakify(self)
展開
#define weakify(...) \
rac_keywordify \
metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)
Step 1: 展開 rac_keywordify
#if DEBUG
#define rac_keywordify autoreleasepool {}
#else#define rac_keywordify try {} @catch (...) {}
#endif
rac_keywordify
也是一個(gè)宏定義溅漾,這個(gè)比較簡(jiǎn)單,分 DEBUG
和非 DEBUG
環(huán)境著榴,這里以 DEBUG
環(huán)境為例來展開
@autoreleasepool {}
metamacro_foreach_cxt(rac_weakify_,, __weak, self)
到這里已經(jīng)可以解釋前面提到的添加 @
符號(hào)的疑問了添履,只是這個(gè) @
符號(hào)用在了 autoreleasepool
的前面,生成了一個(gè)空的 @autoreleasepool{}
而已脑又,然而并沒有什么用暮胧,只是在使用時(shí)前面添加一個(gè) @
符號(hào)锐借,看上去很高大上。
Step 2: 展開 metamacro_foreach_cxt
#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)
metamacro_foreach_cxt
也是一個(gè)宏往衷,展開這一步的結(jié)果如下:
@autoreleasepool {}
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(self))(rac_weakify_, , __weak, self)
這一步中最強(qiáng)大的就是 metamacro_argcount(...)
钞翔,這個(gè)宏可以在預(yù)編譯階段計(jì)算出可變參數(shù)的個(gè)數(shù),如 metamacro_argcount(self
得出的結(jié)果為1席舍, metamacro_argcount(A,B)
得出的結(jié)果為2. 這里對(duì)這個(gè)宏不做詳細(xì)說明布轿,后續(xù)會(huì)另開篇幅單講這個(gè)宏。
Step 3: 展開 metamacro_concat
#define metamacro_concat(A, B) metamacro_concat_(A, B)
#define metamacro_concat_(A, B) A ## B
metamacro_concat
這個(gè)宏做的事情就是把入?yún)⑵唇悠饋恚?metamacro_concat(metamacro_foreach_cxt,metamacro_argcount(self))
展開這部分的結(jié)果是 metamacro_foreach_cxt1
展開這一步的結(jié)果如下:
@autoreleasepool {}metamacro_foreach_cxt1(rac_weakify_, , __weak, self)
Step 4: 展開 metamacro_foreach_cxt1
#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)
在這里来颤,ReactiveCocoa的作者竟然用上一步的入?yún)⑵唇悠饋淼膬?nèi)容重新定義成一個(gè)新的宏汰扭,不僅僅有 metamacro_foreach_cxt1
,還有 metamacro_foreach_cxt0脚曾、2东且、3、4...20
等21個(gè)宏本讥。
繼續(xù)展開這一步的結(jié)果如下:
@autoreleasepool {}
rac_weakify_(0, __weak, self)
注意這個(gè)的 _0
是一個(gè)參數(shù)名珊泳,用在這里指的是 self
。
Step 5: 展開 rac_weakify_
#define rac_weakify_(INDEX, CONTEXT, VAR) \
CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);
metamacro_concat(VAR,_weak_)
這部分展開的結(jié)果是 self_weak_
拷沸,到這一步已經(jīng)比較簡(jiǎn)單了色查,繼續(xù)展開結(jié)果如下:
@autoreleasepool {}
__weak __typeof__(self) self_weak_ = (self);
到這里一個(gè)宏都沒有了,豁然開朗撞芍,看到了熟悉的代碼秧了,最終揭開了神秘的面紗。
既然這么簡(jiǎn)單為什么要繞這么大的圈子呢序无?別著急验毡, weakify(...)
其實(shí)是可以傳入多個(gè)參數(shù)的,最多可以支持傳入20個(gè)呢帝嗡。
如 @weakify(objc1,objc2,objc3...objc20)
晶通,最終展開的結(jié)果是:
@autoreleasepool {}
__weak __typeof__(objc0) self_weak_ = (objc0); __weak __typeof__(objc1) self_weak_ = (objc1); __weak __typeof__(objc2) self_weak_ = (objc2); ... __weak __typeof__(objc19) self_weak_ = (objc19);
到此為止,關(guān)于 weakify(...)
的展開就算完成了哟玷。
下面我們?cè)侔?@strongify(self)
展開
#define strongify(...) \
rac_keywordify \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
metamacro_foreach(rac_strongify_,, __VA_ARGS__) \
_Pragma("clang diagnostic pop")
Step 1: 展開 rac_keywordify
@autoreleasepool {}
metamacro_foreach(rac_strongify_,, self)
Step 2: 展開 metamacro_foreach
#define metamacro_foreach(MACRO, SEP, ...) \
metamacro_foreach_cxt(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__)
把變量替換后展開結(jié)果如下
@autoreleasepool {}
metamacro_foreach_cxt(metamacro_foreach_iter,, rac_strongify_, self)
Step 3: 展開 metamacro_foreach_cxt
這里與 weakify(...)
的Step 2一樣狮辽,展開結(jié)果如下:
@autoreleasepool {}
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(self))(metamacro_foreach_iter, , rac_strongify_, self)
Step 4: 展開 metamacro_concat
這里與 weakify(...)
的Step 3一樣,展開結(jié)果如下:
@autoreleasepool {}
metamacro_foreach_cxt1(metamacro_foreach_iter, , rac_strongify_, self)
Step 5: 展開 metamacro_foreach_cxt1
得到結(jié)果為:
metamacro_foreach_iter(0, rac_strongify_, self)
Step 6: 展開 metamacro_foreach_iter
#define metamacro_foreach_iter(INDEX, MACRO, ARG) MACRO(INDEX, ARG)
得到結(jié)果為 rac_strongify_(0,self)
Step 7: 展開 rac_strongify_
#define rac_strongify_(INDEX, VAR) \
__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);
這里有一點(diǎn)比較特別巢寡,那就是等號(hào)右邊的表達(dá)式 metamacro_concat(VAR,_weak_)
展開后的結(jié)果為 self_weak_
喉脖,這里的 self_weak_
就是 weakify(self)
( __weak __typeof__(self)self_weak_=(self);
)聲明的一個(gè)變量。
最后展開結(jié)果為:
__strong __typeof__(self) self = self_weak_;
到此抑月, strongify(self)
的展開也就完成了树叽;同樣的 strongify(...)
最多也可以支持傳入20個(gè)參數(shù)。
那么開始我們編寫的示例代碼展開后最終結(jié)果如下:
@autoreleasepool {}
__weak __typeof__(self) self_weak_ = (self);
[RACObserve(self.model, name) subscribeNext:^(NSString *name) {
@autoreleasepool {}
__strong __typeof__(self) self = self_weak_;
if(self)
{
self.nameLabel.text = name;
}
}];
宏在項(xiàng)目中的應(yīng)用
1谦絮、NSLog
在項(xiàng)目開發(fā)中我們希望在Release配置下不做日志輸出题诵,這個(gè)比較簡(jiǎn)單
#ifndef __OPTIMIZE__
#define NSLog(format, ...) NSLog((@"[文件名:%s]" "[函數(shù)名:%s]" "[行號(hào):%d]" format), __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
#define NSLog(...){}
#endif
2须误、Singleton
單例也是我們項(xiàng)目中經(jīng)常使用的,如果每個(gè)單例都單獨(dú)實(shí)現(xiàn)一遍仇轻,那么會(huì)有大量的重復(fù)代碼,后續(xù)維護(hù)也會(huì)是災(zāi)難性的奶甘,如果把單例寫成宏來使用篷店,寫起來簡(jiǎn)單,維護(hù)起來也只有一份代碼臭家,何樂而不為呢疲陕?
// Header
#define SINGLETON_HEADER(singletonClassName) \
+ (singletonClassName *)sharedInstance;
// Implementation
#define SINGLETON_IMPLEMENTATION(singletonClassName) \
\
static singletonClassName *_sharedInstance = nil; \
+ (instancetype)sharedInstance { \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_sharedInstance = [[self alloc] init]; \
}); \
return _sharedInstance; \
} \
\
+ (instancetype)allocWithZone:(struct _NSZone *)zone { \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_sharedInstance = [super allocWithZone:zone]; \
});\
return _sharedInstance; \
} \
\
- (id)copyWithZone:(NSZone *)zone { \
return _sharedInstance; \
}
如果再項(xiàng)目中想要定義單例,在頭文件和實(shí)現(xiàn)文件中直接調(diào)用 SINGLETON_HEADER()
和 SINGLETON_IMPLEMENTATION()
就可以搞定钉赁。
3蹄殃、Class
在我們的項(xiàng)目中,基本上所有的源文件都會(huì)帶有我們自己的命名前綴(或者叫命名空間)你踩,那么當(dāng)我們引入別人的開源模塊時(shí)诅岩,如果可以,我們也總是希望加上我們自己的命名前綴來加以區(qū)分带膜;那么既然這樣吩谦,當(dāng)我們自己在寫一些獨(dú)立模塊時(shí)并且希望以后可以以源碼的方式開源出去給別人使用時(shí),我們是否可以為使用者提供一個(gè)便利的解決方案來添加命名前綴呢膝藕?當(dāng)然可以式廷,可以用宏來實(shí)現(xiàn)啊。如下代碼部分摘抄PLCrashReporter 庫中的宏定義如下芭挽。
// .h 文件
#define PL_PREFIX JD
#define PLNS_impl2(prefix, symbol) prefix ## symbol
#define PLNS_impl(prefix, symbol) PLNS_impl2(prefix, symbol)
#define PLNS(symbol) PLNS_impl(PL_PREFIX, symbol)
#define SINGLETON_CLASS PLNS(CartManager)
@interface SINGLETON_CLASS : NSObject
SINGLETON_HEADER(SINGLETON_CLASS)
@end
// .m 文件
@implementation
SINGLETON_CLASSSINGLETON_IMPLEMENTATION(SINGLETON_CLASS)
@end
使用時(shí)和使用普通類沒有什么差別
SINGLETON_CLASS *instance = [SINGLETON_CLASS sharedInstance];
在項(xiàng)目中使用時(shí)我們可以直接使用 SINGLETON_CLASS
滑废,如果需要修改類名直接在.h文件中修改 PLNS
宏的參數(shù)即可,如果要修改名字的前綴可以直接修改 PL_PREFIX
的值袜爪,那么就會(huì)生成一個(gè)帶有特定 PL_PREFIX
前綴的的新類蠕趁,直接把文件Copy走就能使用。
還有很多很多的宏在網(wǎng)上都能查到饿敲,這里就不一一列出了妻导。
總結(jié)
講了這么多宏的優(yōu)勢(shì),同樣宏也存在很明顯的弊端
1怀各、宏是直接嵌入的倔韭,代碼會(huì)相對(duì)多一點(diǎn)
2、嵌套定義過多會(huì)影響程序的可讀性瓢对,且很容易出錯(cuò)
3寿酌、 對(duì)帶參的宏而言,由于是直接替換硕蛹,并不會(huì)檢查參數(shù)是否合法醇疼,存在安全隱患
學(xué)習(xí)了宏的用法硕并,詳細(xì)分析了幾個(gè)比較常用宏的,也了解了宏的弊端秧荆,相信您現(xiàn)在對(duì)宏應(yīng)該有了一個(gè)更高層次的認(rèn)識(shí)倔毙,看到宏也不會(huì)那么模糊了,只需要一步一步展開就能看清其背后的邏輯乙濒,項(xiàng)目中也不僅僅可以用宏來替代一些數(shù)字陕赃,還可以把一些復(fù)雜的高頻出現(xiàn)的代碼用宏來實(shí)現(xiàn)。
參考文獻(xiàn)
- GCC https://gcc.gnu.org/onlinedocs/cpp/Macros.html#Macros
- ReactiveCocoa https://github.com/ReactiveCocoa/ReactiveCocoa
- PLCrashReporter https://www.plcrashreporter.org/
本文非原創(chuàng)