iOS深思篇 | 宏定义

一. 简介

是一种批量处理的称谓,简单来说就是根据定义好的规则替换一定的文本。替换过程在程序编译期,也因此大量使用宏会造成编译时间变长;而且替换过程不进行类型安全检查;还需要注意“边缘效应”;

比如#define N 1 + 2,使用时NSInteger a = N / 2, 预期1.5,结果是2,因为在处理过程中转化为NSInteger a = 1 + 2 / 2,所以建议使用宏时加括号表明是一个整体。

想要了解宏的话得先了解一下来源,OC从C语言演变来,自然也继承了C语言的优良传统,这里简单介绍一下,C语言中预处理命令,它包括三个方面:

  1. 宏定义:#define 指令定义一个宏,#undef指令删除一个宏定义。
  2. 文件包含:#include指令指定一个文件的内容被包含到程序中。
  3. 条件编译:#if,#ifdef,#ifndef,#elif,#else和#endif指令可以根据编译器可以测试的条件来将一段文本包含到程序中或排除在程序之外。

需要注意的是预处理命令都是以符号“#”开头。

1.1 宏的分类

大部分将宏按类型分为对象宏和函数宏,也有按传入参数分为带参数的宏和不带参数的宏。

1.1.1 对象宏
#define STATUS_HEIGHT 20
1.1.2 函数宏
#define MAX(X, Y)  ((X) > (Y) ? (X) : (Y))

1.1 宏定义与常量定义的区别

#defineconst都可用来修饰常量。

  • 编译器处理方式不同
      define宏是在预处理阶段展开。
      const常量是编译运行阶段使用。
  • 类型和安全检查不同
      define宏没有类型,不做任何类型检查,仅仅是展开。
      const常量有具体的类型,在编译阶段会执行类型检查。
  • 存储方式不同
      define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。(宏定义不分配内存,变量定义分配内存。)
      const常量会在内存中分配(可以是堆中也可以是栈中)。
  • const可以节省空间,避免不必要的内存分配。
  • 提高了效率;编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
  • 宏替换只作替换,不做计算,不做表达式求解;

1.2 宏的一些用法

1.2.1 字符化

将传入的单字符参数名转换成字符,以一对单引用括起来.

#define STRING @#s    // 's'
1.2.2 字符串化

在宏参数前加个#,那么在宏体扩展的时候,宏参数会被扩展成字符串的形式。

#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语言中宏定义

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342