宏是一種批量處理的稱謂,簡單來講就是根據定義好的規則替換必定的文本。替換過程在程序編譯期,也所以大量使用宏會形成編譯時間變長;並且替換過程不進行類型安全檢查;還須要注意「邊緣效應」;html
好比#define N 1 + 2
,使用時NSInteger a = N / 2
, 預期1.5
,結果是2
,由於在處理過程當中轉化爲NSInteger a = 1 + 2 / 2
,因此建議使用宏時加括號代表是一個總體。c++
想要了解宏的話得先了解一下來源,OC從C語言演變來,天然也繼承了C語言的優良傳統,這裏簡單介紹一下,C語言中預處理命令,它包括三個方面:數組
- 宏定義:#define 指令定義一個宏,#undef指令刪除一個宏定義。
- 文件包含:#include指令指定一個文件的內容被包含到程序中。
- 條件編譯:#if,#ifdef,#ifndef,#elif,#else和#endif指令能夠根據編譯器能夠測試的條件來將一段文本包含到程序中或排除在程序以外。
須要注意的是預處理命令都是以符號「#」開頭。安全
大部分將宏按類型分爲對象宏和函數宏,也有按傳入參數分爲帶參數的宏和不帶參數的宏。bash
#define STATUS_HEIGHT 20 複製代碼
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) 複製代碼
#define
與const
均可用來修飾常量。markdown
將傳入的單字符參數名轉換成字符,以一對單引用括起來.app
#define STRING @#s // 's' 複製代碼
在宏參數前加個#,那麼在宏體擴展的時候,宏參數會被擴展成字符串的形式。dom
#define NSSTRING #str // "str" 複製代碼
若是宏體所在標示符中有##,那麼在宏體擴展的時候,宏參數會被直接替換到標示符中。ide
#define COMMAND(PREFIX, NAME) PREFIX##NAME 複製代碼
遇到須要換行的能夠用\號鏈接;函數
#define PRINT_IF(CONDITION) \ do { if (CONDITION) \ NSLog(@"print hello"); } \ while (0) 複製代碼
…
和_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語言的宏:
#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中 |
宏 | 描 述 |
---|---|
__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版本 |
/**** 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;\ } 複製代碼
介紹了這麼多宏的相關知識,最後總結下對宏的幾點感想:
- 宏直接調用方法名或者常量名稱的方式易於理解,能夠減小重複代碼,統一規範,方便修改;
- 使用太多宏會增長編譯時長,並且還需注意「邊緣效應」,防止發生不可預期的錯誤;
- 定義宏時應遵照規範,好比宏名和參數的括號間不能有空格;定義表達式要外面用括號包裹等;
暫時先說這麼多,後續還將繼續更新,最後歡迎大佬們下方吹水。