如果你并不是一個new coder
,那你一定是知道#define
的。是的,它并不完美回梧,而且被很多人所詬病。但是的的確確會讓你的代碼看起來看簡潔祖搓,更方便閱讀狱意。下面讓我們先來溫習(xí)下宏的基礎(chǔ)。
基礎(chǔ)宏
定義宏標(biāo)示 (Object-like Macros)
如果你想定義一個緩存區(qū)的大小拯欧,又不太想硬編碼详囤,你可能會選擇下面的方式
#define BUFFER_SIZE 1024
如果你在#define標(biāo)識符后面,使用了BUFFER_SIZE代碼塊開辟一塊區(qū)間
foo = (char *) malloc (BUFFER_SIZE);
C預(yù)處理器將會識別BUFFER_SIZE并且把它替換成你上面寫的1024:
foo = (char *) malloc (1024);
BUFFER_SIZE
=>1024
按照約定,宏定義需要以大寫的形式呈現(xiàn)镐作。這樣讓人瞟一眼就知道你寫的個啥玩意藏姐。
定義宏函數(shù) (Function-like Macros)
你也可以定義一個宏,它看起來像一個函數(shù)一樣该贾。比如:
#import <Foundation/Foundation.h>
#define lang_init() c_init()
void c_init();
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Hello, World!");
lang_init();
}
return 0;
}
void c_init(){
printf("c_init method is called \n");
}
當(dāng)然你也可以去定義一個A減去B
帶參數(shù)的函數(shù):
#define SUB(A,B) (A-B)
驗證一下: SUB(2, SUB(1,3))
干的不錯羔杨,再來一個取兩者更小的一個數(shù)的宏
:
#define MIN(A,B) A<B?A:B
驗證一下:
| 表達式 | 結(jié)果 | 是否正確 |錯誤原因|
| -------- | -----: | :----: ||
| MIN(4,5) | 4 | 是 ||
| MIN(-1杨蛋,5) | -1 | 是 ||
| 2 * MIN(4兜材,5) | 5 | 否 |2*4<5?4:5|
在做乘法的時候我們改變了原先的運算順序,機智如你肯定很快的給出解決方案六荒,如下:
#define MIN(A,B) (A<B?A:B)
信心爆棚的你再次跑起了測試护姆,仿佛一切都在你的掌握之中,直到遇到了那個它:
| 表達式 | 結(jié)果 | 是否正確 ||
| -------- | -----: | :----: ||
| MIN(3, 4 < 5 ? 4 : 5) | 4 | 否 ||
我們先來分解一下:
(3<4<5?4:5?3:4<5?4:5)
// =>(1<5?4:5?3:4<5?4:5)
// =>(1?4:5?3:1?4:5)
// => 4
我們不得不又改了解決方案:
#define MIN(A,B) ((A)<(B)?(A):(B))
說了這么多廢話掏击,只是想提醒大家能加上括號的就一定加括號卵皂。當(dāng)然加了括號也不是萬能了。比如:
float a = 1.0f;
float b = MIN(a++, 1.5f);
不再作展開操作了砚亭,更詳細的可以去看喵大的宏定義的黑魔法
實在感覺到很無力灯变。默默的打開XCODE
,看看蘋果官方的標(biāo)準(zhǔn)寫法:
#define __NSX_PASTE__(A,B) A##B
#if !defined(MIN)
#define __NSMIN_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__(__a,L) : __NSX_PASTE__(__b,L); \
})
#define MIN(A,B) __NSMIN_IMPL__(A,B,__COUNTER__)
#endif
我們先來認識一下幾個不太熟悉的大兄弟:
__COUNTER__
是一個由GCC提供用來構(gòu)造獨立變量名的標(biāo)識符殴玛,在預(yù)編譯過程中從0開始計數(shù),每次被調(diào)用的時候+1).也就是連續(xù)調(diào)用兩次MIN(A,B),兩次的A在預(yù)編譯中會以不同的變量名存在添祸。
#import <Foundation/Foundation.h>
#import "Header.h"
#define FUNC2(x,y) x##y
#define FUNC1(x,y) FUNC2(x,y)
#define FUNC(x) FUNC1(x,__COUNTER__)
int FUNC(my_unique_prefix);
int FUNC(my_unique_prefix);
int main(int argc, const char * argv[]) {
@autoreleasepool {
printf("__COUNTER__==%d \n",__COUNTER__);
printf("__COUNTER__==%d \n",__COUNTER__);
printf("因為預(yù)編譯FUNC被調(diào)用了兩次滚粟,所以__COUNTER__打印的值是從2開始");
}
return 0;
}
=>打印:
__COUNTER__==2
__COUNTER__==3
因為預(yù)編譯FUNC被調(diào)用了兩次刃泌,所以__COUNTER__打印的值是從2開始
__NSX_PASTE__
是預(yù)編譯的連接符凡壤,宏定義中不能直接寫 AB 來連接參數(shù),需要寫成 A##B耙替。
__typeof__
是gcc對C語言的一個擴展保留字亚侠,用于聲明變量類型,var可以是數(shù)據(jù)類型(int, char*..),也可以是變量表達式俗扇。
__NSMIN_IMPL__
現(xiàn)在多加了個__COUNTER__
的參數(shù)生成獨立標(biāo)示符硝烂。
認識了上面的大兄弟,我們現(xiàn)在自己來轉(zhuǎn)換一下铜幽,應(yīng)該就很好讀了:
#define __NSMIN_IMPL__(A,B,L) ({\
__typeof__(A) __a##L = (A);\
__typeof__(B) __b##L = (B);\
(__a##L < __b##L) ? __a##L : __b##L;\
})
宏短語 (Stringification)
有時候你可能想把一個宏的參數(shù)轉(zhuǎn)成一個字符串常量滞谢。可是你并沒有辦法直接把宏參數(shù)硬塞到字符串里面去除抛,這個時候“#”宏預(yù)處理符會幫助到你狮杨。當(dāng)一個宏參數(shù)前面加上“#”宏預(yù)處理符,你就可以成功的把宏參數(shù)轉(zhuǎn)成字符串了到忽。
來個例子禾酱,我們開發(fā)了一個APP,第一個版本的版本號可能是1.0咯
#define VERSION_MAJOR 1
#define VERSION_MINOR 0
#define STRINGSIZE2(s) #s
#define STRINGSIZE(s) STRINGSIZE2(s)
#define VERSION_STRING "v" STRINGSIZE(VERSION_MAJOR) \
"." STRINGSIZE(VERSION_MINOR)
=>打印一下
printf ("%s\n", VERSION_STRING);
v1.0
宏串聯(lián)(Concatenation)
在上面的例子我們有提到過“##”宏串聯(lián)符。假如現(xiàn)在你想整理你電腦里面的動漫绘趋,分為HYRZ和SQBB系列颤陶,后面加上數(shù)字代表集數(shù)。
#define SERIES1 HYRZ
#define SERIES2 SQBB
#define PPCAT_NX(A, B) A ## B
#define PPCAT(A, B) PPCAT_NX(A, B)
#define STRINGSIZE_NX(A) #A
#define STRINGSIZE(A) STRINGSIZE_NX(A)
=>打印一下
printf("%s \n",STRINGSIZE(PPCAT(SERIES1, 01)));
printf("%s \n",STRINGSIZE(PPCAT(SERIES2, 01)));
HYRZ01
SQBB01
可變宏(Variadic Macros)
宏可以和一個方法一樣能接受一系列的參數(shù)陷遮。這面是個例子:
#define eprintf(...) fprintf (stderr, __VA_ARGS__)
這種宏就叫做variadic
滓走。在宏定義(其實也包括函數(shù)定義)的時候,寫為...
的參數(shù)被叫做可變參數(shù)(variadic)帽馋〗练剑可變參數(shù)的個數(shù)不做限定。當(dāng)宏被使用下面的這些參數(shù)會替換這個宏里面的__VA_ARGS__
绽族。所有姨涡,我們可以有下面的展開:
eprintf ("%s:%d: ", __FILE__, __LINE__)
==> fprintf (stderr, "%s:%d: ", __FILE__, __LINE__)
打印出當(dāng)前的文件在你電腦上的路徑,和該行代碼在你當(dāng)前文件的當(dāng)前行吧慢。
再來個相加的加法的例子:
#define A(a1, a2, a3) ((a1)+(a2)+(a3))
#define ADD(...) A(__VA_ARGS__)
printf("結(jié)果為:%d\n", ADD(1, 2, 3));;
=>打印一下
結(jié)果為:6
可以看出你可以在A2
宏里面擴展成N個參數(shù)涛漂。這就證明了__VA_ARGS__
參數(shù)的個數(shù)不做限定。
標(biāo)準(zhǔn)的預(yù)定義宏(Standard Predefined Macros)
表達式 | 含義 |
---|---|
__FILE__ |
當(dāng)前源文件的路徑 |
__LINE__ |
當(dāng)前代碼的在源文件的行數(shù) |
__DATE__ |
當(dāng)前的日期 |
__TIME__ |
當(dāng)前的時間 |
__FUNCTION__ |
當(dāng)前的方法名 |
標(biāo)準(zhǔn)的預(yù)定義宏被相關(guān)語言標(biāo)準(zhǔn)所定義,所以他們適用于各種編譯器匈仗。
表達式 | 含義 |
---|---|
__FILE__ |
當(dāng)前源文件的路徑 |
__LINE__ |
當(dāng)前代碼的在源文件的行數(shù) |
__DATE__ |
當(dāng)前的日期 |
__TIME__ |
當(dāng)前的時間 |
__FUNCTION__ |
當(dāng)前的方法名 |
#define log(x) printf(" THIS IS A LOG : x = %d \n LINE %d \n FILE = %s \n DATE = %s \n TIME = %s \n FUNCTION = %s\n ",x,__LINE__,__FILE__,__DATE__,__TIME__,__FUNCTION__)
=>打印一下
THIS IS A LOG : x = 3
LINE 86
FILE = /Users/wyy/Desktop/wyy/code/demo/DefineDemo/DefineDemo/main.m
DATE = Nov 7 2016
TIME = 15:10:51
FUNCTION = main
Program ended with exit code: 0
宏定義取消(Undefining and Redefining Macros)
如果一個宏不再有用瓢剿,我們可以直接用#undef
來取消它
#define VERSION_NUM 1
int main(int argc, const char * argv[]) {
@autoreleasepool {
#ifdef VERSION_NUM //如果定義了 VERSION_NUM
printf("定義了VERSION_NUM == %d \n",VERSION_NUM);
#endif
#undef VERSION_NUM //取消VERSION_NUM的定義
#ifndef VERSION_NUM //如果沒有定義VERSION_NUM
printf("undef取消了VERSION_NUM的定義 \n");
#endif
}
return 0;
}
所以,那些不用和程序生命周期相綁定的宏悠轩,在使用完后你應(yīng)該取消掉间狂。比如YY系列NSAttributedString+YYText.m
中的宏Fail
:
宏的應(yīng)用
溫故了宏的知識后,我們來看看大牛們是怎么高效的使用宏吧火架。
蘋果官方提供的好用的宏
UIKIT_EXTERN //UIKIT_EXTERN是根據(jù)是否支持C++環(huán)境做的宏定義鉴象,大體類似于extern,使用需要引入<UIKit/UIKit.h>
NS_REQUIRES_SUPER //對于一些可以被繼承的類何鸡,需要子類在重某一調(diào)用父類的實現(xiàn)以保證正確的行為炼列,通過在頭文件方法的聲明末尾添加。如果子類沒有調(diào)用[super method],會有警告提示音比。
NS_AVAILABLE_IOS(_ios) //你可以規(guī)定你自己的方法在iOS版本號適用
NS_DEPRECATED //過期提醒
NS_DESIGNATED_INITIALIZER //Objective-C中主要通過NS_DESIGNATED_INITIALIZER宏來實現(xiàn)指定構(gòu)造器的。
NSLocalizedString(key, comment) \
[[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil] //字符串的本地化
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-相關(guān)命令"
// your code
#pragma clang diagnostic pop
比如:
方法棄用告警
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
不兼容指針類型
#pragma clang diagnostic ignored "-Wincompatible-pointer-types"
循環(huán)引用
#pragma clang diagnostic ignored "-Warc-retain-cycles"
未使用變量
#pragma clang diagnostic ignored "-Wunused-variable"
第三方框架的應(yīng)用
#define MJWeakSelf __weak typeof(self) weakSelf = self; //弱引用
// 運行時objc_msgSend 轉(zhuǎn)函數(shù)指針
#define MJRefreshMsgSend(...) ((void (*)(void *, SEL, UIView *))objc_msgSend)(__VA_ARGS__)
#define MJRefreshMsgTarget(target) (__bridge void *)(target)
#ifndef kSystemVersion
#define kSystemVersion [UIDevice systemVersion] //版本號
#endif
#define YYAssertMainThread() NSAssert([NSThread isMainThread], @"This method must be called on the main thread") //利用斷言實現(xiàn)方法必須在主線程調(diào)用
#define YYCAssertNil(condition, description, ...) NSCAssert(!(condition), (description), ##__VA_ARGS__) //利用斷言保證對象nil氢惋,否則crash
#define YYCAssertNotNil(condition, description, ...) NSCAssert((condition), (description), ##__VA_ARGS__) //利用斷言保證對象非nil洞翩,否則crash
#define dispatch_main_async_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
const
define
有個優(yōu)勢就是沒有類型,所以就算你把下面的ANIMATION_DURATION
賦給int
編譯器也不會產(chǎn)生警告焰望。從另外一個角度來說骚亿,這也是宏的一個弊端。比如說:
當(dāng)你寫動畫效果的時候需要設(shè)定一個動畫展示的時間熊赖。所以代碼可能是這樣的:
#define ANIMATION_DURATION 0.25
很棒来屠,當(dāng)我們的動畫是組合動畫的時候我們也可以通過多次調(diào)用ANIMATION_DURATION
來解決硬編碼的問題。但問題來了震鹉,雖然你的宏命名很規(guī)范俱笛,我可以大概猜出與時間的有關(guān)系,但還是無法確定準(zhǔn)確的類型传趾。所以我們有了更好的選擇:
static const NSTimeInterval kAnimationDuration = 0.25;
#define
最大的優(yōu)勢是它并不需要在你的工程里面占用內(nèi)存(比如上面的ANIMATION_DURATION
迎膜,但0.25
還是需要分配內(nèi)存,放在常量區(qū))浆兰。當(dāng)你使用了這個定義的宏磕仅,它實際上只是用你的設(shè)定的那個值0.25
在編譯的時候去替換。這就造成了簸呈,你使用了多少次這個宏榕订,就需要進行多次替換,占用運行內(nèi)存蜕便。上面講到的__COUNTER__
就保證了每一次調(diào)用同樣的宏劫恒,產(chǎn)生的立即數(shù)都不一樣。
而const
定義的常量儲存在數(shù)據(jù)段轿腺,只有一份copy兼贸,這樣效率更高段直。
更多信息#define
VS const
請點擊這里
文章如果有不足之處,還望指教溶诞!