宏 - 從入門到精通


提起宏定義相信所有人都知道是干什么用的撩荣,在項(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)

最終 SIZEMAX_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++))葱跋,

我們把值替換上看一下娱俺,在比較 1b++ 的時(shí)候荠卷, b++ 的值為 2 然后 b 自增 1 油宜,此時(shí)條件不成立且 b 的值為 3 ,然后再執(zhí)行 b++疼燥,得到 c的值為 3b 自增 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)


本文非原創(chuàng)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末颁股,一起剝皮案震驚了整個(gè)濱河市么库,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌甘有,老刑警劉巖诉儒,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異亏掀,居然都是意外死亡忱反,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門滤愕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缭受,“玉大人,你說我怎么就攤上這事该互∶渍撸” “怎么了?”我有些...
    開封第一講書人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵宇智,是天一觀的道長(zhǎng)蔓搞。 經(jīng)常有香客問我,道長(zhǎng)随橘,這世上最難降的妖魔是什么喂分? 我笑而不...
    開封第一講書人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮机蔗,結(jié)果婚禮上蒲祈,老公的妹妹穿的比我還像新娘。我一直安慰自己萝嘁,他們只是感情好梆掸,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著牙言,像睡著了一般酸钦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咱枉,一...
    開封第一講書人閱讀 52,713評(píng)論 1 312
  • 那天卑硫,我揣著相機(jī)與錄音徒恋,去河邊找鬼。 笑死欢伏,一個(gè)胖子當(dāng)著我的面吹牛入挣,可吹牛的內(nèi)容都是我干的豹爹。 我是一名探鬼主播萝勤,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼距误,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼肾胯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起植捎,我...
    開封第一講書人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后咸这,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡魔眨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年媳维,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遏暴。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡侄刽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出朋凉,到底是詐尸還是另有隱情州丹,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布杂彭,位于F島的核電站墓毒,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏亲怠。R本人自食惡果不足惜所计,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望团秽。 院中可真熱鬧主胧,春花似錦、人聲如沸习勤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽图毕。三九已至己英,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吴旋,已是汗流浹背损肛。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工厢破, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人治拿。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓摩泪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親劫谅。 傳聞我的和親對(duì)象是個(gè)殘疾皇子见坑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 前言 在開發(fā)中荞驴,經(jīng)常在控制器中用到block語句,在block語句中如果需引用self贯城,而self(控制器)對(duì)象中...
    6ffd6634d577閱讀 15,443評(píng)論 5 52
  • 1.自己對(duì)宏的評(píng)價(jià) 宏是C語言系統(tǒng)中最為有趣(通常被稱為強(qiáng)大)的部分熊楼,好的宏定義可以讓代碼簡(jiǎn)潔到極致,事半功倍的效...
    amisarex閱讀 1,629評(píng)論 0 1
  • 一入宏門深似海從此迷路出不來 按照慣例先上代碼 這是RAC中最常用的宏的使用能犯。大家都知道需要成對(duì)使用鲫骗,可傳多個(gè)參數(shù)...
    lattr閱讀 913評(píng)論 0 3
  • 宏,簡(jiǎn)單來說就是按預(yù)定義的規(guī)則來替換相應(yīng)的文本內(nèi)容踩晶,被替換的文本內(nèi)容可以是對(duì)象也可以是函數(shù)执泰。既然是替換,那就需要遵...
    金小俊閱讀 4,688評(píng)論 6 54
  • 0.很長(zhǎng)的前言 在block語句塊中,如果需引用self茸苇,而self對(duì)象中又持有block對(duì)象顿苇,就會(huì)造成循環(huán)引用循...
    ziecho閱讀 1,035評(píng)論 1 7