+++
Categories = ["iOS",]
Tags = ["iOS","macro",]
date = "2014-08-09T11:38:21+08:00"
title = "关于宏"
+++
C中的宏分为两类,对象宏(object-like macro)和函数宏(function-like macro)。对于对象宏来说确实相对简单,但却也不是那么简单的查找替换。对象宏一般用来定义一些常数,举个例子:
#define M_PI 3.14159265358979323846264338327950288
#define
关键字表明即将开始定义一个宏,紧接着的M_PI
是宏的名字,空格之后的数字是内容。类似这样的#define X A
的宏是比较简单的,在编译时编译器会在语义分析认定是宏后,将X替换为A,这个过程称为宏的展开。比如对于上面的M_PI
直接使用。
函数宏顾名思义,就是行为类似函数,可以接受参数的宏。具体来说,在定义的时候,如果我们在宏名字后面跟上一对括号的话,这个宏就变成了函数宏。从最简单的例子开始,比如下面这个函数宏
#define DES(x) x
NSString *name = @"Macro Rookie";
NSLog(@"Hello %@",DES(name));
// => NSLog(@"Hello %@",name);
// => Hello Macro Rookie
这个宏做的事情是,在编译时如果遇到DES
,并且后面带括号,并且括号中的参数个数与定义的相符,那么就将括号中的参数换到定义的内容里去,然后替换掉原来的内容。 具体到这段代码中,DES
接受了一个name
,然后将整个DES(name)
用name
替换掉。多个参数的宏例如这样的:
#define PLUS(x,y) (x + y)
printf("%d",PLUS(3,2));
// => printf("%d",3 + 2);
// => 5
因为宏展开其实是编辑器的预处理,因此它可以在更高层级上控制程序源码本身和编译流程。而正是这个特点,赋予了宏很强大的功能和灵活度。但是凡事都有两面性,在获取灵活的背后,是以需要大量时间投入以对各种边界情况进行考虑来作为代价的。
#define __NSX_PASTE__(A,B) A##B
#define MIN(A,B) __NSMIN_IMPL__(A,B,__COUNTER__)
#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); \
})
可以看出MIN
一共由三个宏定义组合而成。第一个__NSX_PASTE__
里出现的两个连着的井号##
在宏中是一个特殊符号,它表示将两个参数连接起来这种运算。注意函数宏必须是有意义的运算,因此你不能直接写AB
来连接两个参数,而需要写成例子中的A##B
。
接下来是我们调用的两个参数的MIN
,它做的事是调用了另一个三个参数的宏__NSMIN_IMPL__
,其中前两个参数就是我们的输入,而第三个__COUNTER__
是一个预定义的宏,这个值在编译过程中将从0
开始计数,每次被调用时加1
。因为唯一性,所以很多时候被用来构造独立的变量名称。有了上面的基础,再来看最后的实现宏就很简单了。整体思路和前面的实现和之前的GNUC MIN是一样的,区别在于为变量名__a
和__b
添加了一个计数后缀,这样大大避免了变量名相同而导致问题的可能性。
Log
我们通过宏,可以很简单地完成对NSLog原生行为的改进,优雅,高效。只需要在预编译的pch文件中加上
//A better version of NSLog
#define NSLog(format, ...) do { \
fprintf(stderr, "<%s : %d> %s\n", \
[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], \
__LINE__, __func__);\
(NSLog)((format), ##__VA_ARGS__); \
fprintf(stderr, "-------\n"); \
} while (0)
首先是定义部分,第2行的NSLog(format, ...)
。我们看到的是一个函数宏,但是它的参数比较奇怪,第二个参数是...
,在宏定义(其实也包括函数定义)的时候,写为...
的参数被叫做可变参数(variadic)。可变参数的个数不做限定。在这个宏定义中,除了第一个参数format将被单独处理外,接下来输入的参数将作为整体一并看待。回想一下NSLog的用法,我们在使用NSLog时,往往是先给一个format字符串作为第一个参数,然后根据定义的格式在后面的参数里跟上写要输出的变量之类的。这里第一个格式化字符串即对应宏里的format,后面的变量全部映射为...
作为整体处理。
__FILE__
返回当前文件的绝对路径,__LINE__
返回展开该宏时在文件中的行数,__func__
是改宏所在scope的函数名称。我们在做Log输出时如果带上这这三个参数,便可以加快解读Log,迅速定位。关于编译器预定义的Log以及它们的一些实现机制,感兴趣的同学可以移步到gcc文档的PreDefine页面和clang的Builtin Macro进行查看。在这里我们将格式化输出的三个参数分别设定为文件名的最后一个部分(因为绝对路径太长很难看),行数,以及方法名称。
#define NSLogRect(rect) NSLog(@"%s x:%.4f, y:%.4f, w:%.4f, h:%.4f", #rect, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height)
#define NSLogSize(size) NSLog(@"%s w:%.4f, h:%.4f", #size, size.width, size.height)
#define NSLogPoint(point) NSLog(@"%s x:%.4f, y:%.4f", #point, point.x, point.y)
宏
//获取系统版本
#define IOS_VERSION [[[UIDevice currentDevice] systemVersion] floatValue]
#define CurrentSystemVersion [[UIDevice currentDevice] systemVersion]
//获取当前语言
#define CurrentLanguage ([[NSLocale preferredLanguages] objectAtIndex:0])
//判断是否 Retina屏、设备是否%fhone 5、是否是iPad
#define isRetina ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 960), [[UIScreen mainScreen] currentMode].size) : NO)
#define iPhone5 ([UIScreen instancesRespondToSelector:@selector(currentMode)] ? CGSizeEqualToSize(CGSizeMake(640, 1136), [[UIScreen mainScreen] currentMode].size) : NO)
#define isPad (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
// rgb颜色转换(16进制->10进制)
#define UIColorFromRGB(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]
//带有RGBA的颜色设置
#define COLOR(R, G, B, A) [UIColor colorWithRed:R/255.0 green:G/255.0 blue:B/255.0 alpha:A]
// 获取RGB颜色
#define RGBA(r,g,b,a) [UIColor colorWithRed:r/255.0f green:g/255.0f blue:b/255.0f alpha:a]
#define RGB(r,g,b) RGBA(r,g,b,1.0f)
//由角度获取弧度 有弧度获取角度
#define degreesToRadian(x) (M_PI * (x) / 180.0)
#define radianToDegrees(radian) (radian*180.0)/(M_PI)
#define DISPATCH_ONCE_BLOCK(onceBlock) static dispatch_once_t onceToken; dispatch_once(&onceToken, onceBlock);
DISPATCH_ONCE_BLOCK(^{
//code
})