概述
宏,可以简单的理解为一个被赋予名字的代码段。当这个名字出现在代码中被使用的时候,就会被替换成相应的代码段。宏一般可以认为有两种存在(或者说是使用)形式,一种是类似于对象的宏,我们可以不那么严格的说成是宏对象,另一种是类似于方法的宏,我们姑且称之为宏方法。
宏对象
这种形式的宏,我们在使用的时候,可以把它当做一个简单的没有类型的数据源。
宏的创建形式也很简单,用#define
指令,后面跟上宏的名字和被替换的数据源,比如:(需要注意的是宏的名称通常都是用大写字母)
#define TIMEOUT 30
getUserInfoRequest.timeOut = TIMEOUT;
modifyPsdRequest.timeOut = TIMEOUT;
使用的时候直接使用TIMEOUT
就是常量30的意思,这是最基本的替换。
再比如:
#define UIKIT_FONT_LARGE [UIFont systemFontOfSize:16.0f]
firstLabel.font = UIKIT_FONT_LARGE;
这样的宏定义可以规范整个项目的UI,用起来也比较方便。
在宏定义中,如果要换行,使用""符号。
预处理时会认为""后面的还是在同一行,比如:
#define WEEKEND @[ \
@"Saturday", \
@"Sunday", \
]
NSArray *weekend = WEEKEND;
宏可作为一个判断条件,因为它本身可以认为是一段代码,故只需把判断条件定义为宏就行,如:
#define WIDTH_320 (CGRectGetWidth([[UIScreen mainScreen] bounds])==320)
if (WIDTH_320) {
//屏宽为320时,to do something
}else
{
//TODO
}
宏调用时,预处理器在替换宏的内容时,会继续检测内容本身是否也是宏定义,如果是,会继续替换内容。比如上面的那个宏:
#define SCREEN_WIDTH CGRectGetWidth([[UIScreen mainScreen] bounds]) //!< 屏幕宽度
#define WIDTH_320 (SCREEN_WIDTH==320)
将上面的WIDTH_320
展开:
WIDTH_320 ==> (SCREEN_WIDTH==320)
==> (CGRectGetWidth([[UIScreen mainScreen] bounds])==320)
如果一个宏在展开时包含了自己的名字,则不再继续扩展,这样是为了防止无限递归。比如:
#define x (4 + y)
#define y (2 * x)
展开之后如下:
x ==> (4 + y)
==> (4 + (2 * x))
y ==> (2 * x)
==> (2 * (4 + y))
宏方法
我不严格地称宏方法,是因为这种看起来像是一种函数调用。它的定义方式与刚刚那种类似于对象的宏的定义相似,只是在宏名字后面紧跟着一对儿括号,如官方给的例子:
#define lang_init() c_init()
lang_init() ==> c_init()
再比如,React Native的源码中的一段宏定义:
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }
我们扩展来看:
RCT_EXPORT_MODULE(js_name) ==>
RCT_EXTERN void RCTRegisterModule(Class);
+ (NSString *)moduleName
{
return @#js_name;
}
+ (void)load
{
RCTRegisterModule(self);
}
我们在调用RCT_EXPORT_MODULE()
这个宏的时候,其实是调用了三个方法,RCTRegisterModule,moduleName,load,这三个方法具体在各个类里实现了哪些逻辑,我们不去深究,单单一个宏方法就全部囊括了。
上面出现了return @#js_name
,其中js_name是传人宏方法中的参数,#
符号的作用是将宏中的参数转化为字符串,比如传入的参数为commonModule
,则将@#js_name
展开后return的内容就是@"commonModule"
。
再比如,我现在有两个NSMutableDictionary字典,分别为paramsDic和params,我想把paramsDic里的部分数据加到params中,我们的代码可能会这么写:
[params setObject:paramsDic[@"keyName1"] forKey:@"keyName1"];
[params setObject:paramsDic[@"keyName2"] forKey:@"keyName2"];
[params setObject:paramsDic[@"keyName3"] forKey:@"keyName3"];
[params setObject:paramsDic[@"keyName4"] forKey:@"keyName4"];
[params setObject:paramsDic[@"keyName5"] forKey:@"keyName5"];
如果上面的代码,在赋值的时候,做一些判空置为空字符串或者其他判断,就会感觉每次写都写那么长的代码,遇到懒人,他可能用宏定义这么写(判断啥的直接放在宏定义里,我这里没写,仅作参考):
#define PARAMS_ADD(keyName) [params setObject:paramsDic[@#keyName] forKey:@#keyName]
PARAMS_ADD(keyName1);
PARAMS_ADD(keyName2);
PARAMS_ADD(keyName3);
PARAMS_ADD(keyName4);
PARAMS_ADD(keyName5);
还有一个符号##
,它的作用是链接前后两个word。比如官方的例子:
struct command
{
char *name;
void (*function) (void);
};
struct command commands[] =
{
{ "quit", quit_command },
{ "help", help_command },
...
};
写成宏是这个样子的:
#define COMMAND(NAME) { #NAME, NAME ## _command }
struct command commands[] =
{
COMMAND (quit),
COMMAND (help),
...
};
再比如:
#define DEFINE_SINGLETON_FOR_CLASS(className) \
\
+ (className *)sharedManager { \
static className *shared##className = nil; \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
@synchronized(self){ \
shared##className = [[self alloc] init]; \
} \
}); \
return shared##className; \
}
假如传人的参数是MyClass,上面的shared##className
即为sharedMyClass
;
多参数宏
一个宏可以接受数量可变的参数声明一个函数。比如官方给的例子:
#define eprintf(...) fprintf (stderr, __VA_ARGS__)
宏方法声明时,括号里为...
表明这种宏可接受数量可变的参数,宏调用时,其参数列表的所有命名参数,包括任何逗号,成为变量参数,替换标识符VA_ARGS出现出现的位置。因此,我们对上面这个宏方法使用后扩展如下:
eprintf ("%s:%d: ", input_file, lineno)
==> fprintf (stderr, "%s:%d: ", input_file, lineno)
再比如:
#define eprintf(format, ...) fprintf (stderr, format, __VA_ARGS__)
1.
eprintf("success!\n", );
==> fprintf(stderr, "success!\n", );
仔细看上面的1,是不是发现了什么,官方的描述是这样的:
This formulation looks more descriptive, but unfortunately it is less flexible: you must now supply at least one argument after the format string. In standard C, you cannot omit the comma separating the named argument from the variable arguments. Furthermore, if you leave the variable argument empty, you will get a syntax error, because there will be an extra comma after the format string.
大概说的是上面的那种宏定义方式不够灵活,说你要在格式化的字符串后面至少跟一个参数,逗号分隔符也不能省略。不然就像1的那样,后面跟着一个逗号和空格,当然解决办法还是有的,还是用上面提到的##
,我们修改下宏定义,如下:
#define eprintf(format, ...) fprintf (stderr, format, ##__VA_ARGS__)
再来看调用的展开:
eprintf ("success!\n")
==> fprintf(stderr, "success!\n");
这里##
的作用是连接前后两个字符时,发现后面的是空字符,就删掉前面的,
分隔符。
取消宏定义
使用#undef
指令可取消宏定义,#undef
接受单个参数,也就是宏的名字。直接看官方例子:
#define FOO 4
x = FOO; ==> x = 4;
#undef FOO
x = FOO; ==> x = FOO;
需要说明一点,宏定义中的,空格出现在相同的地方,一个空格跟多个空格的并没有差别,比如:
#define fx(x) (x + x)
等同于
#define fx(x) (x + x)
最后,想了解更详细更官方的宏知识,请戳这里。