iOS中的一些特殊语法记录

本篇文章来解释一些iOS代码中的特殊语法、写法,语法糖

1. FBKVOKeyPath

1
2
3
#define FBKVOKeyPath(KEYPATH) \
@(((void)(NO && ((void)KEYPATH, NO)), \
({ const char *fbkvokeypath = strchr(#KEYPATH, '.'); NSCAssert(fbkvokeypath, @"Provided key path is invalid."); fbkvokeypath + 1; })))

摘录自KVOControllerFBKVOController.h
官方解释如下:

1
2
3
4
5
6
7
8
9
10
This macro ensures that key path exists at compile time.
Given a real receiver with a key path as you would call it, it verifies at compile time that the key path exists, without calling it.

For example:

FBKVOKeyPath(string.length) => @"length"

Or even the complex case:

FBKVOKeyPath(string.lowercaseString.length) => @"lowercaseString.length".

我们可以使用该宏定义获取string.property中的property属性,而且,默认转换成NSString类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1 校验传入的KeyPath是否有编译错误
((void)(NO && ((void)KEYPATH, NO))
// NO && ... 是为了运行时直接返回NO减少操作, 因为有(void)KEYPATH的存在,所以编译时校验了object.property

// 2 将传入的object.property转换为"property"
{ const char *fbkvokeypath = strchr(#KEYPATH, '.'); NSCAssert(fbkvokeypath, @"Provided key path is invalid."); fbkvokeypath + 1; }
// 2.1 #KEYPATH将object.property转为字符串"object.property"
// 2.2 strchr截取".property"
// 2.3 NSCAssert保证fbkvokeypath有效
// 2.4 ".property"+1="property"

// 3 使用@()语法糖将char *转换为NSString类型
@(((void)NO, "property"))
// 因为','操作符是返回后面的值,即string = (@"a", @"b");string的值为@"b"

2.1 附:c语言中,##表示把两个宏参数贴合在一起,而单个#的功能是将其后面的宏参数进行字符串化操作
2.2 strchr()函数定义:
char *strchr( const char *str, int ch );
str: 传入的字符串指针
ch:要搜索的字符
return:返回特定字符**第一次**在字符串中的出现的指针,未找到时返回空指针
2.3 NSCAssert和NSAssert区别,见2
2.4 p+1 表示地址,指针p所指向的内存地址的下一个内存地址。

2. NSAssert和NSCAssert区别

在苹果的SDK中可以看到这两个都是定义的宏

1
2
3
4
5
6
7
8
9
10
11
#define NSAssert(condition, desc, ...)  \  
do { \
__PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
if (!(condition)) { \
[[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \
object:self file:[NSString stringWithUTF8String:__FILE__] \
lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \
} \
__PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
} while(0)
#endif

1
2
3
4
5
6
7
8
9
10
11
#define NSCAssert(condition, desc, ...) \  
do { \
__PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
if (!(condition)) { \
[[NSAssertionHandler currentHandler] handleFailureInFunction:[NSString stringWithUTF8String:__PRETTY_FUNCTION__] \
file:[NSString stringWithUTF8String:__FILE__] \
lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \
} \
__PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
} while(0)
#endif

都是使用断言来保证条件的正确性
但是要注意一点:
NSAssert在实现中,强引用了self当前对象,这里有可能会引起循环引用

3. iOS枚举中直接设置值为字母

一般情况,我们写枚举like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef NS_ENUM(NSInteger, UIViewAnimationTransition) {  
UIViewAnimationTransitionNone,//默认从0开始
UIViewAnimationTransitionFlipFromLeft,
UIViewAnimationTransitionFlipFromRight,
UIViewAnimationTransitionCurlUp,
UIViewAnimationTransitionCurlDown,
};

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0,
UIViewAutoresizingFlexibleWidth = 1 << 1,
UIViewAutoresizingFlexibleRightMargin = 1 << 2,
UIViewAutoresizingFlexibleTopMargin = 1 << 3,
UIViewAutoresizingFlexibleHeight = 1 << 4,
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};

但是,还会有这样的写法
比如:

1
2
3
4
5
6
7
typedef NS_ENUM(NSInteger, Test)
{
TestA = 0,
TestB = 1,
TestC = 4,
TestD = G,
};

这种情况,就是使用了”G”的ASCII,赋值给了TestD

4. ReactiveCocoa中的RAC宏定义

先贴个连接:https://www.jianshu.com/p/7086e090069d

参考资料1:KVOController详解
参考资料2:NSAssert,NSCassert
参考资料3:断言(NSAssert)的使用
参考资料4:iOS之枚举用法
参考资料5:Hello, 宏定义魔法世界1