iOS深思篇 | 宏定義

一. 簡介

是一種批量處理的稱謂,簡單來講就是根據定義好的規則替換必定的文本。替換過程在程序編譯期,也所以大量使用宏會形成編譯時間變長;並且替換過程不進行類型安全檢查;還須要注意「邊緣效應」;html

好比#define N 1 + 2,使用時NSInteger a = N / 2, 預期1.5,結果是2,由於在處理過程當中轉化爲NSInteger a = 1 + 2 / 2,因此建議使用宏時加括號代表是一個總體。c++

想要了解宏的話得先了解一下來源,OC從C語言演變來,天然也繼承了C語言的優良傳統,這裏簡單介紹一下,C語言中預處理命令,它包括三個方面:數組

  1. 宏定義:#define 指令定義一個宏,#undef指令刪除一個宏定義。
  2. 文件包含:#include指令指定一個文件的內容被包含到程序中。
  3. 條件編譯:#if,#ifdef,#ifndef,#elif,#else和#endif指令能夠根據編譯器能夠測試的條件來將一段文本包含到程序中或排除在程序以外。

須要注意的是預處理命令都是以符號「#」開頭。安全

1.1 宏的分類

大部分將宏按類型分爲對象宏和函數宏,也有按傳入參數分爲帶參數的宏和不帶參數的宏。bash

1.1.1 對象宏
#define STATUS_HEIGHT 20
複製代碼
1.1.2 函數宏
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
複製代碼

1.1 宏定義與常量定義的區別

#defineconst均可用來修飾常量。app

  • 編譯器處理方式不一樣   define宏是在預處理階段展開。   const常量是編譯運行階段使用。
  • 類型和安全檢查不一樣   define宏沒有類型,不作任何類型檢查,僅僅是展開。   const常量有具體的類型,在編譯階段會執行類型檢查。
  • 存儲方式不一樣   define宏僅僅是展開,有多少地方使用,就展開多少次,不會分配內存。(宏定義不分配內存,變量定義分配內存。)   const常量會在內存中分配(能夠是堆中也能夠是棧中)。
  • const能夠節省空間,避免沒必要要的內存分配。
  • 提升了效率;編譯器一般不爲普通const常量分配存儲空間,而是將它們保存在符號表中,這使得它成爲一個編譯期間的常量,沒有了存儲與讀內存的操做,使得它的效率也很高。
  • 宏替換隻做替換,不作計算,不作表達式求解;

1.2 宏的一些用法

1.2.1 字符化

將傳入的單字符參數名轉換成字符,以一對單引用括起來.dom

#define STRING @#s // 's'
複製代碼
1.2.2 字符串化

在宏參數前加個#,那麼在宏體擴展的時候,宏參數會被擴展成字符串的形式。ide

#define NSSTRING #str // "str"
複製代碼
1.2.3 鏈接

若是宏體所在標示符中有##,那麼在宏體擴展的時候,宏參數會被直接替換到標示符中。函數

#define COMMAND(PREFIX, NAME) PREFIX##NAME 
複製代碼
1.2.4 換行

遇到須要換行的能夠用\號鏈接;學習

#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__ : 至少傳一個參數 ##__VA_ARGS__ : 隨便傳幾個參數

二. C語言的宏

下面是一些C語言的宏:

#define 定義一個預處理宏
#undef 取消宏的定義
#include 包含文件命令
#include_next 與#include類似, 但它有着特殊的用途
#if 編譯預處理中的條件命令, 至關於C語法中的if語句
#ifdef 判斷某個宏是否被定義, 若已定義, 執行隨後的語句
#ifndef 與#ifdef相反, 判斷某個宏是否未被定義
#elif 若#if, #ifdef, #ifndef或前面的#elif條件不知足, 則執行#elif以後的語句, 至關於C語法中的else-if
#else 與#if, #ifdef, #ifndef對應, 若這些條件不知足, 則執行#else以後的語句, 至關於C語法中的else
#endif #if, #ifdef, #ifndef這些條件命令的結束標誌.
#defined 與#if, #elif配合使用, 判斷某個宏是否被定義
#line 標誌該語句所在的行號
# 將宏參數替代爲以參數值爲內容的字符竄常量
## 將兩個相鄰的標記(token)鏈接爲一個單獨的標記
#pragma 說明編譯器信息
#warning 顯示編譯警告信息
#error 顯示編譯錯誤信息
複製代碼

是否是感受很熟悉,就算在iOS開發中也常常用到裏面的內容。 除了基本的宏操做還有預約義宏,預約義宏是爲了方便處理一些有用的信息,裏面定義了一些預處理標識符,也就是預約義宏。預約義宏的名稱都是以「__」(兩條下劃線)開頭和結尾的,若是宏名是由兩個單詞組成,那麼中間以「_」(一條下劃線)進行鏈接。而且,宏名稱通常都由大寫字符組成。 下面是常見的預約義宏:

描 述
FUNTION 獲取當前函數名
DATE 丐前源文件的編澤口期,用 「Mmm dd yyy」形式的字符串常量表示
FILE 當前源文件的名稱,用字符串常量表示
LINE 當前源義件中的行號,用十進制整數常量表示,它能夠隨#line指令改變
TIME 當前源文件的最新編譯吋間,用「hh:mm:ss」形式的寧符串常量表示
STDC 若是今前編澤器符合ISO標準,那麼該宏的值爲1,不然未定義
COUNTER 無重複的計數器,從程序啓動開始每次調用都會++,經常使用語宏中定義無重複的參數名稱
func 所在scope的函數名稱,常見於log中

三. OC相關宏的擴展

3.1 系統相關宏

描 述
__has_include 用來檢查是否引入了某個文件
NS_ASSUME_NONNULL_BEGIN & NS_ASSUME_NONNULL_END 在這兩個宏之間的代碼,全部簡單指針對象都被假定爲nonnull
__cplusplus 識別是c代碼仍是c++代碼
__has_feature(objc_arc) 判斷是不是ARC,不然爲MRC
@available(iOS 11, *) 當前iOS11是否知足需求
TARGET_IPHONE_SIMULATOR 知足條件時,執行模擬器代碼;不然執行非模擬器代碼
__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_8_0 設備系統大於8.0 以上的代碼
NS_REQUIRES_SUPER 申明子類若是重寫該方法,必須調用該父類方法
FOUNDATION_EXPORT 用於定義常量,在檢測值是否相等時直接比較指針,效率比較快
NS_AVAILABLE_IOS(8_0) 這個方法能夠在iOS3.0及之後的版本中使用,若是在比5.0更老的版本中調用這個方法,就會引發崩潰
NS_DEPRECATED_IOS(2_0, 6_0) 這個方法在iOS2.0引入,6.0被刪除
NS_AVAILABLE(10_8, 6_0) 這個宏告訴咱們這方法分別隨Mac OS 10.8和iOS 6.0被引入
NS_DEPRECATED(10_0, 10_6, 2_0, 4_0) 這個方法隨Mac OS 10.0和iOS 2.0被引入,在Mac OS 10.6和iOS 4.0後被廢棄
NS_CLASS_AVAILABLE(10_11, 9_0) 這個類分別隨Mac OS 10.11和iOS9.0被引入
NS_ENUM_AVAILABLE(10_11, 9_0) 這個枚舉分別隨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
//根據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;\
}\

// 狀態欄高度
#define kStatusBarHeight (IPHONE_X ? 44.f : 20.f)
// 頂部導航欄高度
#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
// 去除上下導航欄剩餘中間視圖高度
#define ContentHeight (kScreenHeight - kSafeAreaTopHeight - kSafeAreaBottomHeight - kTabbarHeight)


/**** 顏色 ****/
//隨機顏色
#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]
//十六進制顏色
#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]
//十六進制顏色,透明度
#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)]

/**** 系統相關 ****/
//app版本號
#define DEVICE_APP_VERSION (NSString *)[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]
//app Build版本號
#define DEVICE_APP_BUILD (NSString *)[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]
//系統版本號(string)
#define DEVICE_OS_VERSION [[UIDevice currentDevice] systemVersion]
//系統版本號(float#define DEVICE_OS_VERSION_VALUE [DEVICE_OS_VERSION floatValue]
//檢測是不是豎屏狀態
#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]

/**** 數據判空 ****/
//字符串是否爲空
#define kStringIsEmpty(str) ([str isKindOfClass:[NSNull class]] || str == nil || [str length] < 1 ? YES : NO )
//數組是否爲空
#define kArrayIsEmpty(array) (array == nil || [array isKindOfClass:[NSNull class]] || array.count == 0)
//字典是否爲空
#define kDictIsEmpty(dic) (dic == nil || [dic isKindOfClass:[NSNull class]] || dic.allKeys == 0)
//是不是空對象
#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;
//強引用
#define ZBStrongSelf(type) __strong typeof(type) type = weak##type;

//角度轉換弧度
#define ZBDegreesToRadian(x) (M_PI * (x) / 180.0)
//弧度轉換角度
#define ZBRadianToDegrees(radian) (radian*180.0)/(M_PI)

//block判空回調
#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;\
}
複製代碼

四. 總結

介紹了這麼多宏的相關知識,最後總結下對宏的幾點感想:

  1. 宏直接調用方法名或者常量名稱的方式易於理解,能夠減小重複代碼,統一規範,方便修改;
  2. 使用太多宏會增長編譯時長,並且還需注意「邊緣效應」,防止發生不可預期的錯誤;
  3. 定義宏時應遵照規範,好比宏名和參數的括號間不能有空格;定義表達式要外面用括號包裹等;

暫時先說這麼多,後續還將繼續更新,最後歡迎大佬們下方吹水。

學習:

GCC Macros

宏--從入門到精通

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

const常量與define宏定義的區別

宏定義的黑魔法 - 宏菜鳥起飛手冊

C語言編譯預處理和條件編譯執行過程的理解

C語言宏定義的幾個坑和特殊用法

C語言中宏定義的使用

深刻理解C語言中宏定義

相關文章
相關標籤/搜索