iOS深思篇 | 宏定義

一. 簡(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è)方面:

  1. 宏定義:#define 指令定義一個(gè)宏,#undef指令刪除一個(gè)宏定義。
  2. 文件包含:#include指令指定一個(gè)文件的內(nèi)容被包含到程序中。
  3. 條件編譯:#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ū)別

#defineconst都可用來(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)感想:

  1. 宏直接調(diào)用方法名或者常量名稱的方式易于理解,可以減少重復(fù)代碼,統(tǒng)一規(guī)范,方便修改;
  2. 使用太多宏會(huì)增加編譯時(shí)長(zhǎng),而且還需注意“邊緣效應(yīng)”,防止發(fā)生不可預(yù)期的錯(cuò)誤;
  3. 定義宏時(shí)應(yīng)遵守規(guī)范,比如宏名和參數(shù)的括號(hào)間不能有空格;定義表達(dá)式要外面用括號(hào)包裹等;

暫時(shí)先說(shuō)這么多,后續(xù)還將繼續(xù)更新,最后歡迎大佬們下方吹水。

學(xué)習(xí):

GCC Macros

宏--從入門到精通

【如何正確使用const,static,extern】|那些人追的干貨

const常量與define宏定義的區(qū)別

宏定義的黑魔法 - 宏菜鳥(niǎo)起飛手冊(cè)

C語(yǔ)言編譯預(yù)處理和條件編譯執(zhí)行過(guò)程的理解

C語(yǔ)言宏定義的幾個(gè)坑和特殊用法

C語(yǔ)言中宏定義的使用

深入理解C語(yǔ)言中宏定義

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

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