在日常的开发中,我们或多或少的会使用一些宏定义来提高我们变成的效率。那么什么是宏定义呢?通俗一点,宏定义就是对某些代码进行替换,在预编译的时候将定义的值又会换回去。
宏定义的分类
宏定义可以分为两类,一类称为对象宏,一类称为函数宏。
对象宏就是比较简单的常量定义。
#define K_SHORT_ANIMATION_TIME 0.3f
函数宏则是比较复杂,具有函数计算的定义,作用也是一个函数。
#define debug_NSLog(format, ...) NSLog((@"<文件名: %@-行数:(%d)> 方法名: %s: " format), [[NSString stringWithUTF8String:__FILE__] lastPathComponent], __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__);
iOS
常见的宏定义
-
__FILE__
:当前源文件名。 -
__LINE__
:当前行数。 -
__DATE__
:当前的编译日期。 -
__TIME__
:当前编译时间。 -
__func__
是C99
的标准,在GCC
编译器只输出函数名称 -
__FUNCTION__
的作用同__func__
-
__PRETTY_FUNCTION__
是一个非标准宏,会以<return-type> <class-name>::<member-function-name>(<parameters-list>)
输出,即会输出返回类型、类名、函数名、参数列表。 -
__VA_ARGS__
:可变参数宏,能够使用可以变化的参数列表。比如上述打印的封装,...
表示可变参数,它把可变参数传给宏中的__VA_ARGS__
。 -
##
:链接参数。
RAC
中的宏定义
在RAC
这个库中,有很多非常有特点的宏,其中实现的想法很值得我们借鉴。如weakify(self)
定义了弱类型的self
。
首先,我们看到的宏如下:
#define weakify(...) \
rac_keywordify \
metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)
rac_keywordify
的定义如下,可以暂时不用管。
#if DEBUG
#define rac_keywordify autoreleasepool {}
#else
#define rac_keywordify try {} @catch (...) {}
#endif
而metamacro_foreach_cxt
的定义如下:
// MACRO = rac_weakify_
// SEP = 空
// CONTEXT = __weak
// ... = self
// __VA_ARGS__ = self
#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)
#define metamacro_concat(A, B) \
metamacro_concat_(A, B)
#define metamacro_concat_(A, B) A ## B
metamacro_concat
的意思是我们要将后面的参数拼接起来,那么我们先来看看metamacro_argcount
做了什么:
// ... = self
// metamacro_at(20, self, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
#define metamacro_argcount(...) \
metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
// N = 20
#define metamacro_at(N, ...) \
metamacro_concat(metamacro_at, N)(__VA_ARGS__)
// metamacro_concat(metamacro_at, 20)(self, 20, 19, ...)
// metamacro_at20(self, 20, 19, ...)
#define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__)
// metamacro_head(1)
#define metamacro_head(...) \
metamacro_head_(__VA_ARGS__, 0)
#define metamacro_head_(FIRST, ...) FIRST
最终metamacro_argcount(__VA_ARGS__)
返回的是可变参数__VA_ARGS__
的参数个数,在本例中也就是1。此时metamacro_foreach_cxt
就变成了:
metamacro_concat(metamacro_foreach_cxt, 1)(MACRO, SEP, CONTEXT, __VA_ARGS__)
整理一下:
metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, __VA_ARGS__)
#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)
结合当前的例子就变成了:
rac_weakify_(0, __weak, self);
#define rac_weakify_(INDEX, CONTEXT, VAR) \
CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);
最终就变成了:
__weak __typeof__(self) self_weak_ = self
所以,weakify(self)
的最终实现就是__weak __typeof__(self) self_weak_ = self
。
在RAC
中还有很多类型的宏定义,比如说strongify(...)
、RACObserve
等。
使用宏定义的优缺点
优点:
- 提高了程序的可读性,比如说我们在代码中大量使用一个常数,使用宏定义清晰的给其命名,别人再来阅读代码的时候,就会简单明了些。比如:
#define SCREEN_WIDTH UIScreen.mainScreen.bounds.size.width
#define MAX_SIZE 100
- 方便进行修改,用户只需要在一处定义,多处使用,统一修改。比如我们定义一种颜色作为我们页面的主题色,后期需要整体改动色值,那么我们只需要修改宏定义即可。
#define K_THEME_COLOR [UIColor colorWithRed:(r)/255.0f green:(g)/255.0f blue:(b)/255.0f alpha:(a)]
- 提高程序的运行效率。使用带参的宏定义既可完成函数调用的功能,又能避免函数的出栈与入栈操作,减少系统开销,提高运行效率,如果有一个函数会在项目中频繁使用,可以考虑一下宏定义。
#define SXRGB16Color(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]
缺点
- 过于复杂的宏导致代码不易读。
- 对带参的宏而言,由于是直接替换,并不会检查参数是否合法,存在安全隐患,预编译语句仅仅是简单的值代替,缺乏类型的检测机制。
- 未考虑充分的宏定义可能存在
BUG
。
比如,我们定义了一个比较大小的宏,按照下面的例子执行下来却是有很多错误。
#define TMIN(A,B) A < B ? A : B
- (void)defineTest {
int a = 2 * TMIN(2, 3);
NSLog(@"==a==%d==", a);
int b = TMIN(3, 4 < 5 ? 4 : 5);
NSLog(@"==b==%d==", b);
float c = 1.0;
float d = TMIN(c++, 1.5);
NSLog(@"==d==%f==", d);
}

官方给出的比较大小的宏如下:
#if !defined(MIN)
#define __NSMIN_IMPL__(A,B,L) ({ __typeof__(A) __NSX_PASTE__(__a,L) = (A); __typeof__(B) __NSX_PASTE__(__b,L) = (B); (__NSX_PASTE__(__a,L) < __NSX_PASTE__(__b,L)) ? __NSX_PASTE__(__a,L) : __NSX_PASTE__(__b,L); })
#define MIN(A,B) __NSMIN_IMPL__(A,B,__COUNTER__)
#endif
总结
使用宏定义可以给我们的开发带来方便,但是大量的使用宏,则会带来效率上的影响。另外定义宏的时候,一定要考虑其正确性、健壮性、易读性。
网友评论