一. 簡(jiǎn)介
宏是一種批量處理的稱謂,簡(jiǎn)單來(lái)說(shuō)就是根據(jù)定義好的規(guī)則替換一定的文本。替換過(guò)程在程序編譯期,也因此大量使用宏會(huì)造成編譯時(shí)間變長(zhǎng);而且替換過(guò)程不進(jìn)行類型安全檢查;還需要注意“邊緣效應(yīng)”;
比如#define N 1 + 2,使用時(shí)NSInteger a = N / 2, 預(yù)期1.5,結(jié)果是2,因?yàn)樵谔幚磉^(guò)程中轉(zhuǎn)化為NSInteger a = 1 + 2 / 2,所以建議使用宏時(shí)加括號(hào)表明是一個(gè)整體。
想要了解宏的話得先了解一下來(lái)源,OC從C語(yǔ)言演變來(lái),自然也繼承了C語(yǔ)言的優(yōu)良傳統(tǒng),這里簡(jiǎn)單介紹一下,C語(yǔ)言中預(yù)處理命令,它包括三個(gè)方面:
- 宏定義:#define 指令定義一個(gè)宏,#undef指令刪除一個(gè)宏定義。
- 文件包含:#include指令指定一個(gè)文件的內(nèi)容被包含到程序中。
- 條件編譯:#if,#ifdef,#ifndef,#elif,#else和#endif指令可以根據(jù)編譯器可以測(cè)試的條件來(lái)將一段文本包含到程序中或排除在程序之外。
需要注意的是預(yù)處理命令都是以符號(hào)“#”開(kāi)頭。
1.1 宏的分類
大部分將宏按類型分為對(duì)象宏和函數(shù)宏,也有按傳入?yún)?shù)分為帶參數(shù)的宏和不帶參數(shù)的宏。
1.1.1 對(duì)象宏
#define STATUS_HEIGHT 20
1.1.2 函數(shù)宏
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
1.1 宏定義與常量定義的區(qū)別
#define與const都可用來(lái)修飾常量。
- 編譯器處理方式不同
define宏是在預(yù)處理階段展開(kāi)。
const常量是編譯運(yùn)行階段使用。 - 類型和安全檢查不同
define宏沒(méi)有類型,不做任何類型檢查,僅僅是展開(kāi)。
const常量有具體的類型,在編譯階段會(huì)執(zhí)行類型檢查。 - 存儲(chǔ)方式不同
define宏僅僅是展開(kāi),有多少地方使用,就展開(kāi)多少次,不會(huì)分配內(nèi)存。(宏定義不分配內(nèi)存,變量定義分配內(nèi)存。)
const常量會(huì)在內(nèi)存中分配(可以是堆中也可以是棧中)。 - const可以節(jié)省空間,避免不必要的內(nèi)存分配。
- 提高了效率;編譯器通常不為普通const常量分配存儲(chǔ)空間,而是將它們保存在符號(hào)表中,這使得它成為一個(gè)編譯期間的常量,沒(méi)有了存儲(chǔ)與讀內(nèi)存的操作,使得它的效率也很高。
- 宏替換只作替換,不做計(jì)算,不做表達(dá)式求解;
1.2 宏的一些用法
1.2.1 字符化
將傳入的單字符參數(shù)名轉(zhuǎn)換成字符,以一對(duì)單引用括起來(lái).
#define STRING @#s // 's'
1.2.2 字符串化
在宏參數(shù)前加個(gè)#,那么在宏體擴(kuò)展的時(shí)候,宏參數(shù)會(huì)被擴(kuò)展成字符串的形式。
#define NSSTRING #str // "str"
1.2.3 連接
如果宏體所在標(biāo)示符中有##,那么在宏體擴(kuò)展的時(shí)候,宏參數(shù)會(huì)被直接替換到標(biāo)示符中。
#define COMMAND(PREFIX, NAME) PREFIX##NAME
1.2.4 換行
遇到需要換行的可以用\號(hào)連接;
#define PRINT_IF(CONDITION) \
do { if (CONDITION) \
NSLog(@"print hello"); } \
while (0)
1.2.5 變參宏(… 和_VA_ARGS)
下面是OC中自定義Log的例子:
#ifdef DEBUG
#define Log(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
#define Log(...)
#endif
需要注意的是:
__VA_ARGS__ : 至少傳一個(gè)參數(shù)
##__VA_ARGS__ : 隨便傳幾個(gè)參數(shù)
二. C語(yǔ)言的宏
下面是一些C語(yǔ)言的宏:
#define 定義一個(gè)預(yù)處理宏
#undef 取消宏的定義
#include 包含文件命令
#include_next 與#include相似, 但它有著特殊的用途
#if 編譯預(yù)處理中的條件命令, 相當(dāng)于C語(yǔ)法中的if語(yǔ)句
#ifdef 判斷某個(gè)宏是否被定義, 若已定義, 執(zhí)行隨后的語(yǔ)句
#ifndef 與#ifdef相反, 判斷某個(gè)宏是否未被定義
#elif 若#if, #ifdef, #ifndef或前面的#elif條件不滿足, 則執(zhí)行#elif之后的語(yǔ)句, 相當(dāng)于C語(yǔ)法中的else-if
#else 與#if, #ifdef, #ifndef對(duì)應(yīng), 若這些條件不滿足, 則執(zhí)行#else之后的語(yǔ)句, 相當(dāng)于C語(yǔ)法中的else
#endif #if, #ifdef, #ifndef這些條件命令的結(jié)束標(biāo)志.
#defined 與#if, #elif配合使用, 判斷某個(gè)宏是否被定義
#line 標(biāo)志該語(yǔ)句所在的行號(hào)
# 將宏參數(shù)替代為以參數(shù)值為內(nèi)容的字符竄常量
## 將兩個(gè)相鄰的標(biāo)記(token)連接為一個(gè)單獨(dú)的標(biāo)記
#pragma 說(shuō)明編譯器信息
#warning 顯示編譯警告信息
#error 顯示編譯錯(cuò)誤信息
是不是感覺(jué)很熟悉,就算在iOS開(kāi)發(fā)中也經(jīng)常用到里面的內(nèi)容。
除了基本的宏操作還有預(yù)定義宏,預(yù)定義宏是為了方便處理一些有用的信息,里面定義了一些預(yù)處理標(biāo)識(shí)符,也就是預(yù)定義宏。預(yù)定義宏的名稱都是以“_”(兩條下劃線)開(kāi)頭和結(jié)尾的,如果宏名是由兩個(gè)單詞組成,那么中間以“”(一條下劃線)進(jìn)行連接。并且,宏名稱一般都由大寫字符組成。
下面是常見(jiàn)的預(yù)定義宏:
| 宏 | 描 述 |
|---|---|
| FUNTION | 獲取當(dāng)前函數(shù)名 |
| DATE | 丐前源文件的編澤口期,用 “Mmm dd yyy”形式的字符串常量表示 |
| FILE | 當(dāng)前源文件的名稱,用字符串常量表示 |
| LINE | 當(dāng)前源義件中的行號(hào),用十進(jìn)制整數(shù)常量表示,它可以隨#line指令改變 |
| TIME | 當(dāng)前源文件的最新編譯吋間,用“hh:mm:ss”形式的寧符串常量表示 |
| STDC | 如果今前編澤器符合ISO標(biāo)準(zhǔn),那么該宏的值為1,否則未定義 |
| COUNTER | 無(wú)重復(fù)的計(jì)數(shù)器,從程序啟動(dòng)開(kāi)始每次調(diào)用都會(huì)++,常用語(yǔ)宏中定義無(wú)重復(fù)的參數(shù)名稱 |
| func | 所在scope的函數(shù)名稱,常見(jiàn)于log中 |
三. OC相關(guān)宏的擴(kuò)展
3.1 系統(tǒng)相關(guān)宏
| 宏 | 描 述 |
|---|---|
| __has_include | 用來(lái)檢查是否引入了某個(gè)文件 |
| NS_ASSUME_NONNULL_BEGIN & NS_ASSUME_NONNULL_END | 在這兩個(gè)宏之間的代碼,所有簡(jiǎn)單指針對(duì)象都被假定為nonnull |
| __cplusplus | 識(shí)別是c代碼還是c++代碼 |
| __has_feature(objc_arc) | 判斷是否是ARC,否則為MRC |
| @available(iOS 11, *) | 當(dāng)前iOS11是否滿足需求 |
| TARGET_IPHONE_SIMULATOR | 滿足條件時(shí),執(zhí)行模擬器代碼;否則執(zhí)行非模擬器代碼 |
| __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_8_0 | 設(shè)備系統(tǒng)大于8.0 以上的代碼 |
| NS_REQUIRES_SUPER | 申明子類如果重寫該方法,必須調(diào)用該父類方法 |
| FOUNDATION_EXPORT | 用于定義常量,在檢測(cè)值是否相等時(shí)直接比較指針,效率比較快 |
| NS_AVAILABLE_IOS(8_0) | 這個(gè)方法可以在iOS3.0及以后的版本中使用,如果在比5.0更老的版本中調(diào)用這個(gè)方法,就會(huì)引起崩潰 |
| NS_DEPRECATED_IOS(2_0, 6_0) | 這個(gè)方法在iOS2.0引入,6.0被刪除 |
| NS_AVAILABLE(10_8, 6_0) | 這個(gè)宏告訴我們這方法分別隨Mac OS 10.8和iOS 6.0被引入 |
| NS_DEPRECATED(10_0, 10_6, 2_0, 4_0) | 這個(gè)方法隨Mac OS 10.0和iOS 2.0被引入,在Mac OS 10.6和iOS 4.0后被廢棄 |
| NS_CLASS_AVAILABLE(10_11, 9_0) | 這個(gè)類分別隨Mac OS 10.11和iOS9.0被引入 |
| NS_ENUM_AVAILABLE(10_11, 9_0) | 這個(gè)枚舉分別隨Mac OS 10.11和iOS9.0被引入 |
| __IPHONE_OS_VERSION_MAX_ALLOWED | 允許最大的iOS版本 |
| __IPHONE_OS_VERSION_MIN_ALLOWED | 最低的iOS版本 |
3.2 自定義的宏
/**** UI尺寸 ****/
//獲取屏幕寬度與高度
#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
#define SCREENH_HEIGHT [UIScreen mainScreen].bounds.size.height
//根據(jù)6,7,8適配
#define ScaleWidth(width) (width / 375.0) * SCREEN_WIDTH
#define ScaleHeight(height) (height / 667.0) * SCREENH_HEIGHT
//是否是iPhoneX
#define k1IS_iPhoneX (SCREEN_WIDTH == 375.f && SCREENH_HEIGHT == 812.f)
#define k2IS_iPhoneX ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(1125, 2436), [[UIScreen mainScreen] currentMode].size) : NO)
//判斷是否為X系列
#define IPHONE_X \
({BOOL isPhoneX = NO;\
if (@available(iOS 11.0, *)) {\
isPhoneX = [[UIApplication sharedApplication] delegate].window.safeAreaInsets.bottom > 0.0;\
}\
// 狀態(tài)欄高度
#define kStatusBarHeight (IPHONE_X ? 44.f : 20.f)
// 頂部導(dǎo)航欄高度
#define kNavigationBarHeight 44.f
// 頂部安全距離
#define kSafeAreaTopHeight (IPHONE_X ? 88.f : 64.f)
// 底部安全距離
#define kSafeAreaBottomHeight (IPHONE_X ? 34.f : 0.f)
// Tabbar高度
#define kTabbarHeight 49.f
// 去除上下導(dǎo)航欄剩余中間視圖高度
#define ContentHeight (kScreenHeight - kSafeAreaTopHeight - kSafeAreaBottomHeight - kTabbarHeight)
/**** 顏色 ****/
//隨機(jī)顏色
#define ZBRandomColor [UIColor colorWithRed:arc4random_uniform(256)/255.0 green:arc4random_uniform(256)/255.0 blue:arc4random_uniform(256)/255.0 alpha:1.0]
//RGB
#define ZBRGBColor(r, g, b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1.0]
//RGBA
#define ZBRGBAColor(r, g, b, a) [UIColor colorWithRed:(r)/255.0 green:(r)/255.0 blue:(r)/255.0 alpha:a]
//十六進(jìn)制顏色
#define ZBRGBHex(rgbValue) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:1.0]
//十六進(jìn)制顏色,透明度
#define ZBRGBHexAlpha(rgbValue,a) [UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 green:((float)((rgbValue & 0xFF00) >> 8))/255.0 blue:((float)(rgbValue & 0xFF))/255.0 alpha:(a)]
/**** 系統(tǒng)相關(guān) ****/
//app版本號(hào)
#define DEVICE_APP_VERSION (NSString *)[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]
//app Build版本號(hào)
#define DEVICE_APP_BUILD (NSString *)[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]
//系統(tǒng)版本號(hào)(string)
#define DEVICE_OS_VERSION [[UIDevice currentDevice] systemVersion]
//系統(tǒng)版本號(hào)(float)
#define DEVICE_OS_VERSION_VALUE [DEVICE_OS_VERSION floatValue]
//檢測(cè)是否是豎屏狀態(tài)
#define IsPortrait ([UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortrait || [UIApplication sharedApplication].statusBarOrientation == UIInterfaceOrientationPortraitUpsideDown)
/**** 沙盒目錄文件 ****/
//temp
#define ZBPathTemp NSTemporaryDirectory()
//Document
#define ZBPathDocument [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]
//Cache
#define ZBPathCache [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]
/**** 數(shù)據(jù)判空 ****/
//字符串是否為空
#define kStringIsEmpty(str) ([str isKindOfClass:[NSNull class]] || str == nil || [str length] < 1 ? YES : NO )
//數(shù)組是否為空
#define kArrayIsEmpty(array) (array == nil || [array isKindOfClass:[NSNull class]] || array.count == 0)
//字典是否為空
#define kDictIsEmpty(dic) (dic == nil || [dic isKindOfClass:[NSNull class]] || dic.allKeys == 0)
//是否是空對(duì)象
#define kObjectIsEmpty(_object) (_object == nil \
|| [_object isKindOfClass:[NSNull class]] \
|| ([_object respondsToSelector:@selector(length)] && [(NSData *)_object length] == 0) \
|| ([_object respondsToSelector:@selector(count)] && [(NSArray *)_object count] == 0))
/**** 常用縮寫 ****/
#define kApplication [UIApplication sharedApplication]
#define kKeyWindow [UIApplication sharedApplication].keyWindow
#define kAppDelegate [UIApplication sharedApplication].delegate
#define kUserDefaults [NSUserDefaults standardUserDefaults]
#define kNotifCenter [NSNotificationCenter defaultCenter]
/**** 其他 ****/
//弱引用
#define ZBWeak __weak typeof(self) weakSelf = self;
#define ZBWeakSelf(type) __weak typeof(type) weak##type = type;
//強(qiáng)引用
#define ZBStrongSelf(type) __strong typeof(type) type = weak##type;
//角度轉(zhuǎn)換弧度
#define ZBDegreesToRadian(x) (M_PI * (x) / 180.0)
//弧度轉(zhuǎn)換角度
#define ZBRadianToDegrees(radian) (radian*180.0)/(M_PI)
//block判空回調(diào)
#define ZBBlockNotEmpt(block, ...) if (block) { block(__VA_ARGS__); }
//.h頭文件中的單例宏
#define ZBSingletonH(name) + (instancetype)shared##name;
//.m文件中的單例宏
#define ZBSingletonM(name) \
static id _instance;\
+ (instancetype)allocWithZone:(struct _NSZone *)zone{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance = [super allocWithZone:zone];\
});\
return _instance;\
}\
+ (instancetype)shared##name{\
static dispatch_once_t onceToken;\
dispatch_once(&onceToken, ^{\
_instance = [[self alloc] init];\
});\
return _instance;\
}\
- (id)copyWithZone:(NSZone *)zone{\
return _instance;\
}
四. 總結(jié)
介紹了這么多宏的相關(guān)知識(shí),最后總結(jié)下對(duì)宏的幾點(diǎn)感想:
- 宏直接調(diào)用方法名或者常量名稱的方式易于理解,可以減少重復(fù)代碼,統(tǒng)一規(guī)范,方便修改;
- 使用太多宏會(huì)增加編譯時(shí)長(zhǎng),而且還需注意“邊緣效應(yīng)”,防止發(fā)生不可預(yù)期的錯(cuò)誤;
- 定義宏時(shí)應(yīng)遵守規(guī)范,比如宏名和參數(shù)的括號(hào)間不能有空格;定義表達(dá)式要外面用括號(hào)包裹等;
暫時(shí)先說(shuō)這么多,后續(xù)還將繼續(xù)更新,最后歡迎大佬們下方吹水。
學(xué)習(xí):
【如何正確使用const,static,extern】|那些人追的干貨