Ekulelu's Blog

iOS宏

注明:本篇blog是参考多篇blog后的一个整理汇总,另外加上了自己常用的一些宏定义。如有冒犯,请会知,将删除该部分。

求最小值

宏在OC中使用是很多的,官方也定义了大量的宏。宏简单来讲就是替换。但是这个替换里面稍不注意还是很多坑的。比如举一个获取最小值的宏。

1
2
//Version 1.0
#define MIN(A,B) A < B ? A : B

这样写你就惨了,比如遇到下面的情况:

1
int a = 2 * MIN(3, 4); //输出a=4

具体自行将宏替换后就知道发生了什么,所以出了下面的版本。

1
2
//Version 2.0
#define MIN(A,B) (A < B ? A : B)

这个版本你依然继续崩溃吧,比如遇到下面的例子

1
int a = MIN(3, 4 < 5 ? 4 : 5); //a=4

好,再来

1
2
//Version 3.0
#define MIN(A,B) ((A) < (B) ? (A) : (B))

看似应该完美了,但,总有刁民想害朕!如下:

1
2
3
float a = 1.0f;
float b = MIN(a++, 1.5f);
// => float b = ((a++) < (1.5f) ? (a++) : (1.5f))

此时应该有翻桌子的图。
为了解决这个问题,需要用到GNU C的赋值扩展,即({}),会将括号里面的最后一个表达式的赋值返回。例子如下:

1
2
3
4
5
6
int a = ({
int b = 1;
int c = 2;
b + c;
});
// => a is 3

然后就可以解决刚刚的min问题

1
2
//GNUC MIN
#define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b;})

最后来看看clang中的MIN的写法,如下

1
2
3
4
5
6
7
8
9
#define __NSX_PASTE__(A,B) A##B
#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

你会发现这里是一大堆。先来解释一下,首先看第一个宏

1
#define __NSX_PASTE__(A,B) A##B

“##”的意思是将A和B连接起来。因为宏中的参数都是有意义的,要把AB连起来不能直接写成AB。有时候你还会看到这样的语句:##A,也就是##前没有变量。这里需要提到##的另外一个特性,就是##后面的变量A为空的时候,那么它会将前面一个逗号吃掉。这里还有一个单井号#顺便提一下,这个的意思是将后面的参数字符串话,也就是替换之后在这个参数左右加上””,另外由于OC中的字符串是需要加@的。所以一般会看到写成这样@#expression。
现在我们明白了,这个宏就是一个拼接函数。
再来看最后一个宏

1
#define MIN(A,B) __NSMIN_IMPL__(A,B,__COUNTER__)

COUNTER是一个预定义的宏,这个值在编译过程中将从0开始计数,每次被调用时加1。因为唯一性,所以很多时 候被用来构造独立的变量名称。这里的作用在这里(L就是COUNTER),也就是将定义的参数名字后面加了一个常数。

1
__typeof__(A) __NSX_PASTE__(__a,L) = (A);

这确保了定义的参数名是会变的。有博客说这里是为了避免同一scope里面重复出现a,怕的是使用宏的语句之前有定义了a。但我手动测试发现(XCode8),即使在前面定义了a,在宏的语句里面也不会出现问题。debug里面可以发现同时存在了两个变量a。我猜测是({})将宏限定在了一个scope里面。根据{}会屏蔽外部同名变量的原则,即使在宏里面定义同名变量也没问题。
好了,MIN的宏就到此为止。

while?

如果有经常看宏的同学应该会发现这样的写法:

1
2
3
#define MyMacro(format, ...) do {
....... //很多行语句
} while (0)

先解释一下宏参数列表里面的…,这三个点代表的是可变参数,就是可以任意个,包括0个。然后你可以用VA_ARGS来代表这一堆的可变参数。当然你还可以写成 name… ,这样的话,需要使用name来代替可变参数。
然后再来看这个写成while(0)的do…while是做什么用。
考虑一下下面的语句:

1
2
if (a > 4)
MyMacro(@"%d", a);

假如没有do…while,懂了吧,宏展开之后,if对应的执行语句就只有一行。如果if后面还有else的话,那么直接就报错了。
有人说,那么用括号吧宏括起来就好了吧,确实可以,但是宏后面那个分号,如果在有else的情况下,会成了下面的样子。

1
2
3
4
5
if (a > 4) {
......//宏里面的语句
}; else {
.....
}

看到else前面那个分号没,此时应该有捂脸哭的表情。于是有了do…while的写法,这个语句可以把那个悲催的分号吃掉。这样就可以把这个宏当成一句语句来使用了。(此时想起了swift可以不用分号的好处,这不知能节省多少劳动力,但swift没宏,捂脸哭)

宏和常量的区别:

宏只是简单的替换,如果是字符串的话,建议使用常量定义,这样只有编译一次。
常量字符串定义:
在.h文件中

1
UIKIT_EXTERN NSString * const MY_TOKEN; // UIKIT_EXTERN是苹果的宏,区分了C和C++。

在.m文件中

1
NSString * const MY_TOKEN = @"xxxxx";

常用宏

最后举例一些常用宏:

1、获取屏幕尺寸,横屏竖屏都可以。

1
2
#define BOUNDS_WIDTH [UIScreen mainScreen].bounds.size.width
#define BOUNDS_HEIGHT [UIScreen mainScreen].bounds.size.height

2、状态栏和导航栏的高度

1
2
#define STATUS_BAR_HEIGHT CGRectGetHeight([[UIApplication sharedApplication] statusBarFrame])
#define NAVIGATION_BAR_HEIGHT CGRectGetHeight(self.navigationController.navigationBar.frame)

3、颜色

1
2
3
4
5
6
7
8
9
10
11
#define UIColorFromRGB(rgbValue) \
[UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 \
green:((float)((rgbValue & 0x00FF00) >> 8))/255.0 \
blue:((float)((rgbValue & 0x0000FF) >> 0))/255.0 \
alpha:1.0]
#define UIColorFromRGBWithAlpha(rgbValue,alphaValue) \
[UIColor colorWithRed:((float)((rgbValue & 0xFF0000) >> 16))/255.0 \
green:((float)((rgbValue & 0x00FF00) >> 8))/255.0 \
blue:((float)((rgbValue & 0x0000FF) >> 0))/255.0 \
alpha:alphaValue]

4、设备类型

1
2
3
4
5
6
7
8
9
#define IS_IPAD (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
#define IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
#define IS_RETINA ([[UIScreen mainScreen] scale] >= 2.0)
#define iOS8Later ([UIDevice currentDevice].systemVersion.floatValue >= 8.0f)
#define iOS9Later ([UIDevice currentDevice].systemVersion.floatValue >= 9.0f)
#define IS_IPHONE_4_OR_LESS (IS_IPHONE && SCREEN_MAX_LENGTH < 568.0)
#define IS_IPHONE_5 (IS_IPHONE && SCREEN_MAX_LENGTH == 568.0)
#define IS_IPHONE_6 (IS_IPHONE && SCREEN_MAX_LENGTH == 667.0)
#define IS_IPHONE_6P_OR_MORE (IS_IPHONE && SCREEN_MAX_LENGTH >= 736.0)

5、iOS版本

1
2
#define iOS8Later ([UIDevice currentDevice].systemVersion.floatValue >= 8.0f)
#define iOS9Later ([UIDevice currentDevice].systemVersion.floatValue >= 9.0f)

6、弱引用宏,这个用得很多。这里很巧妙地用autoreleasepool和try来加了一个@。注意着两对宏要同时使用,在block外面用@weakify(A)后,在block里面使用@strongify(A)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#ifndef weakify
#define weakify
#if DEBUG
#if __has_feature(objc_arc)
#define weakify(object) autoreleasepool{} __weak __typeof__(object) weak##_##object = object;
#else
#define weakify(object) autoreleasepool{} __block __typeof__(object) block##_##object = object;
#endif
#else
#if __has_feature(objc_arc)
#define weakify(object) try{} @finally{} {} __weak __typeof__(object) weak##_##object = object;
#else
#define weakify(object) try{} @finally{} {} __block __typeof__(object) block##_##object = object;
#endif
#endif
#endif
#ifndef strongify
#define strongify
#if DEBUG
#if __has_feature(objc_arc)
#define strongify(object) autoreleasepool{} __typeof__(object) object = weak##_##object;
#else
#define strongify(object) autoreleasepool{} __typeof__(object) object = block##_##object;
#endif
#else
#if __has_feature(objc_arc)
#define strongify(object) try{} @finally{} __typeof__(object) object = weak##_##object;
#else
#define strongify(object) try{} @finally{} __typeof__(object) object = block##_##object;
#endif
#endif
#endif

7、单例宏,再也不用写一堆代码了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// .h文件
#define RYSingletonH + (instancetype)sharedInstance;
#define RYSingletonHWithNonnull + (nonnull instancetype)sharedInstance;
// .m文件
#define RYSingletonM \
static id _instance = nil; \
+ (id)allocWithZone:(struct _NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [super allocWithZone:zone]; \
}); \
return _instance; \
} \
+ (instancetype)sharedInstance \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [[self alloc] init]; \
}); \
return _instance; \
} \
- (id)copyWithZone:(NSZone *)zone \
{ \
return _instance; \
}
#define RYSingletonMWithNonnull \
static id _instance = nil; \
+ (id)allocWithZone:(struct _NSZone *)zone \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [super allocWithZone:zone]; \
}); \
return _instance; \
} \
+ (nonnull instancetype)sharedInstance \
{ \
static dispatch_once_t onceToken; \
dispatch_once(&onceToken, ^{ \
_instance = [[self alloc] init]; \
}); \
return _instance; \
} \
- (id)copyWithZone:(NSZone *)zone \
{ \
return _instance; \
}

8、懒加载宏,只针对能简单init的类。至于为啥不在这里addSubview,见我另外一篇博客《iOS懒加载的坑》。

1
2
3
4
5
6
7
#define LazyLoad(Type, name) \
- (Type *)name{ \
if (_##name == nil) { \
_##name = [[Type alloc] init]; \
} \
return _##name; \
}

9、获取沙盒路径

1
2
3
4
5
6
//获取temp
#define kPathTemp NSTemporaryDirectory()
//获取沙盒 Document
#define kPathDocument [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]
//获取沙盒 Cache
#define kPathCache [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]

10、Log。Release版本不编译打印语句,而且打印语句有类、方法、文件的第几行信息。

1
2
3
4
5
#ifdef DEBUG
# define DLog(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
# define DLog(...)
#endif

RAC里面还有一个能判断参数有多少个的宏,可以参考这篇博客
http://www.cocoachina.com/ios/20140621/8905.html