你應該掌握的#define宏定義

如果你并不是一個new coder ,那你一定是知道#define的。是的,它并不完美,而且被很多人所詬病。但是的的確確會讓你的代碼看起來看簡潔,更方便閱讀。下面讓我們先來溫習下宏的基礎。

基礎宏

定義宏標示 (Object-like Macros)

如果你想定義一個緩存區(qū)的大小,又不太想硬編碼,你可能會選擇下面的方式

#define BUFFER_SIZE 1024

如果你在#define標識符后面,使用了BUFFER_SIZE代碼塊開辟一塊區(qū)間

 foo = (char *) malloc (BUFFER_SIZE);

C預處理器將會識別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");
}

當然你也可以去定義一個A減去B帶參數(shù)的函數(shù):

#define SUB(A,B) (A-B)

驗證一下: SUB(2, SUB(1,3))

1.png

干的不錯,再來一個取兩者更小的一個數(shù)的宏

#define MIN(A,B) A<B?A:B

驗證一下:

| 表達式 | 結果 | 是否正確 |錯誤原因|
| -------- | -----: | :----: ||
| 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)

信心爆棚的你再次跑起了測試,仿佛一切都在你的掌握之中,直到遇到了那個它:

| 表達式 | 結果 | 是否正確 ||
| -------- | -----: | :----: ||
| 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))

說了這么多廢話,只是想提醒大家能加上括號的就一定加括號。當然加了括號也不是萬能了。比如:

float a = 1.0f;
float b = MIN(a++, 1.5f);

不再作展開操作了,更詳細的可以去看喵大的宏定義的黑魔法
實在感覺到很無力。默默的打開XCODE,看看蘋果官方的標準寫法:

#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提供用來構造獨立變量名的標識符,在預編譯過程中從0開始計數(shù),每次被調(diào)用的時候+1).也就是連續(xù)調(diào)用兩次MIN(A,B),兩次的A在預編譯中會以不同的變量名存在。

#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("因為預編譯FUNC被調(diào)用了兩次,所以__COUNTER__打印的值是從2開始");
    }
    return 0;
}

=>打?。?__COUNTER__==2 
__COUNTER__==3 
因為預編譯FUNC被調(diào)用了兩次,所以__COUNTER__打印的值是從2開始

__NSX_PASTE__ 是預編譯的連接符,宏定義中不能直接寫 AB 來連接參數(shù),需要寫成 A##B。
__typeof__ 是gcc對C語言的一個擴展保留字,用于聲明變量類型,var可以是數(shù)據(jù)類型(int, char*..),也可以是變量表達式。
__NSMIN_IMPL__現(xiàn)在多加了個__COUNTER__的參數(shù)生成獨立標示符。
認識了上面的大兄弟,我們現(xiàn)在自己來轉(zhuǎn)換一下,應該就很好讀了:

#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ù)硬塞到字符串里面去,這個時候“#”宏預處理符會幫助到你。當一個宏參數(shù)前面加上“#”宏預處理符,你就可以成功的把宏參數(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ù)不做限定。當宏被使用下面的這些參數(shù)會替換這個宏里面的__VA_ARGS__。所有,我們可以有下面的展開:

eprintf ("%s:%d: ", __FILE__, __LINE__)
==>  fprintf (stderr, "%s:%d: ", __FILE__, __LINE__)

打印出當前的文件在你電腦上的路徑,和該行代碼在你當前文件的當前行。

再來個相加的加法的例子:

#define A(a1, a2, a3) ((a1)+(a2)+(a3))
#define ADD(...) A(__VA_ARGS__)

printf("結果為:%d\n", ADD(1, 2, 3));;
=>打印一下

結果為:6

可以看出你可以在A2宏里面擴展成N個參數(shù)。這就證明了__VA_ARGS__參數(shù)的個數(shù)不做限定。

標準的預定義宏(Standard Predefined Macros)

表達式 含義
__FILE__ 當前源文件的路徑
__LINE__ 當前代碼的在源文件的行數(shù)
__DATE__ 當前的日期
__TIME__ 當前的時間
__FUNCTION__ 當前的方法名

標準的預定義宏被相關語言標準所定義,所以他們適用于各種編譯器。

表達式 含義
__FILE__ 當前源文件的路徑
__LINE__ 當前代碼的在源文件的行數(shù)
__DATE__ 當前的日期
__TIME__ 當前的時間
__FUNCTION__ 當前的方法名
#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;
}

所以,那些不用和程序生命周期相綁定的宏,在使用完后你應該取消掉。比如YY系列NSAttributedString+YYText.m中的宏Fail:

2.png

宏的應用

溫故了宏的知識后,我們來看看大牛們是怎么高效的使用宏吧。

蘋果官方提供的好用的宏

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)指定構造器的。

NSLocalizedString(key, comment) \
        [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil] //字符串的本地化

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-相關命令"
    // 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"  

第三方框架的應用



#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)生警告。從另外一個角度來說,這也是宏的一個弊端。比如說:
當你寫動畫效果的時候需要設定一個動畫展示的時間。所以代碼可能是這樣的:

#define ANIMATION_DURATION 0.25

很棒,當我們的動畫是組合動畫的時候我們也可以通過多次調(diào)用ANIMATION_DURATION來解決硬編碼的問題。但問題來了,雖然你的宏命名很規(guī)范,我可以大概猜出與時間的有關系,但還是無法確定準確的類型。所以我們有了更好的選擇:

static const NSTimeInterval kAnimationDuration = 0.25;

#define 最大的優(yōu)勢是它并不需要在你的工程里面占用內(nèi)存(比如上面的ANIMATION_DURATION,但0.25還是需要分配內(nèi)存,放在常量區(qū))。當你使用了這個定義的宏,它實際上只是用你的設定的那個值0.25在編譯的時候去替換。這就造成了,你使用了多少次這個宏,就需要進行多次替換,占用運行內(nèi)存。上面講到的__COUNTER__就保證了每一次調(diào)用同樣的宏,產(chǎn)生的立即數(shù)都不一樣。
const定義的常量儲存在數(shù)據(jù)段,只有一份copy,這樣效率更高。
更多信息#define VS const請點擊這里

文章如果有不足之處,還望指教!

參考資料:

GCC_MACROS
宏定義的黑魔法
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容