RAC宏
一、基础宏
1.metamacro_stringify
#define metamacro_stringify(VALUE) \
metamacro_stringify_(VALUE)
#define metamacro_stringify_(VALUE) # VALUE
这样写的目的是预防参数中传入宏定以后,以宏定义的名字做为参数
#define number 10
#define add(a,b) add_(a,b) //先取得宏的值
#define add_(a,b) (a ## 101 ## b)//拼接
2.metamacro_concat(A, B)
#define metamacro_concat(A, B) \
metamacro_concat_(A, B)
#define metamacro_concat_(A, B) A ## B
拼接两个参数
3.metamacro_argcount(...) 和 metamacro_at(N, ...)
#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)
#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__)
#define metamacro_head(...) \
metamacro_head_(__VA_ARGS__, 0)
#define metamacro_head_(FIRST, ...) FIRST
#define metamacro_head(FIRST,..., 0) FIRST
第一个是获取参数数量,第二是获取参数的第一个参数,
metamacro_argcount 变参传入后,通过在后面拼接20个参数,通过metamacro_head_宏来获取20位置的数值,此为参数个数
设计灵感来自P99社库
4.metamacro_foreach(MACRO, SEP, ...) 和 metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)
#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)
展开后等价于
metamacro_foreach_cxtN(MACRO, SEP, CONTEXT, __VA_ARGS__)
#define metamacro_foreach_cxt20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \
metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \
SEP \
MACRO(19, CONTEXT, _19)
这个宏是为了批量处理一些宏定义,中间加上一个参数
Sep
MACRO(19, CONTEXT, _19) 传入参数,宏,宏之间的间隔(SEP)
,还有变参,来进行批量操作
define metamacro_foreach(MACRO, SEP, ...) \
metamacro_foreach_cxt(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__)
#define weakify(...) \
rac_keywordify \
metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)
#define strongify(...) \
rac_keywordify \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
metamacro_foreach(rac_strongify_,, __VA_ARGS__) \
_Pragma("clang diagnostic pop")
MACRO(19, _19) 传入参数,宏,宏之间的间隔(SEP)
,还有变参,来进行批量操作,少传入了context的参数
5.metamacro_foreach_cxt_recursive(MACRO, SEP, CONTEXT, ...)
#define metamacro_foreach_cxt_recursive20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \
metamacro_foreach_cxt_recursive19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \
SEP \
MACRO(19, CONTEXT, _19)
跟metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)一样
由于宏在递归展开中可能会导致递归前置条件失败,在这种情况下,应该使用这个递归宏。当然,它的效果和metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)宏是完全一样的。
6.metamacro_foreach_concat(BASE, SEP, ...)
BASE_0 \
SEP \
BASE_1 \
SEP \
BASE_2 \
SEP \
BASE_3 \
……
……
……
……
……
……
SEP \
BASE_N - 4 \
SEP \
BASE_N - 3 \
SEP \
metamacro_foreach_concat(BASE, SEP, ...)宏如同它的名字一样,把可变参数里面每个参数都拼接到BASE后面,每个参数拼接完成之间都用SEP分隔。
试想一种场景:
如果有一连串的方法,方法名都有一个相同的前缀,后面是不同的。这种场景下,利用metamacro_foreach_concat(BASE, SEP, ...)宏是非常爽的,它会一口气组合出相关的一列表的不同的宏
7.metamacro_for_cxt(COUNT, MACRO, SEP, CONTEXT)
#define metamacro_for_cxt(COUNT, MACRO, SEP, CONTEXT) \
metamacro_concat(metamacro_for_cxt, COUNT)(MACRO, SEP, CONTEXT)
metamacro_for_cxtN(MACRO, SEP, CONTEXT)
#define metamacro_for_cxtN(MACRO, SEP, CONTEXT) \
metamacro_for_cxtN - 1(MACRO, SEP, CONTEXT) \
SEP \
MACRO(N - 1, CONTEXT)
这个宏的用途是执行COUNT次MACRO宏命令,每次MACRO宏命令的第一个参数都会从COUNT开始递减到0。
8.metamacro_tail(...) 的作用就是取出可变参数列表除去第一个参数以外的所有参数。(至少两个参数)
9.etamacro_head(...) 的作用就是取出可变参数列表的第一个参数。(至少一个参数)
10.metamacro_take(N, ...)
#define metamacro_take20(...) metamacro_head(__VA_ARGS__), metamacro_take19(metamacro_tail(__VA_ARGS__))
取出变参前n个元素
11.metamacro_drop(N, ...)
#define metamacro_drop20(...) metamacro_drop19(metamacro_tail(__VA_ARGS__))
取出变参一个数量后的参数
12. metamacro_dec(VAL) 和 metamacro_inc(VAL)
第一个取得前一位数,后面的取得后一位数
13. metamacro_if_eq(A, B)
NSInteger a = metamacro_if_eq(3, 3)(({NSLog(@"1");3;}))(({NSLog(@"1231312");4;}));
NSLog(@"%ld", a);
比较两个数,然后确定哪个方法
14.metamacro_if_eq_recursive(A, B)
同上
15.metamacro_is_even(N)
N的值域在[0,20]之间。
取偶
#define metamacro_is_even(N) \
metamacro_at(N, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1)
16. metamacro_not(B)
只能取1
#define metamacro_not(B) \
metamacro_at(B, 1, 0)
二、 常用宏
1.RACTuplePack_class_name
跟metamacro_argcount类似,取得RACTuple的类名
RACTuplePack_object_or_ractuplenil 根据传入参数是否为nil,转换为RACTuple
2.RACObserve(TARGET, KEYPATH)
#define RACObserve(TARGET, KEYPATH) \
({ \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \
__weak id target_ = (TARGET); \
[target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \
_Pragma("clang diagnostic pop") \
})
#define keypath(...) \
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__))(keypath1(__VA_ARGS__))(keypath2(__VA_ARGS__))
#define keypath1(PATH) \
(((void)(NO && ((void)PATH, NO)), strchr(# PATH, '.') + 1))
#define keypath2(OBJ, PATH) \
(((void)(NO && ((void)OBJ.PATH, NO)), # PATH))
- 加void是为了不会出现警告
- 加NO是C语言判断条件短路表达式。增加NO && 以后,预编译的时候看见了NO,就会很快的跳过判断条件。
- strchr函数原型如下:
extern char *strchr(const char *s,char c);
查找字符串s中首次出现字符c的位置。返回首次出现字符c的位置的指针,返回的地址是被查找 字符串指针开始的第一个与字符c相同字符的指针,如果字符串中不存在字符c则返回NULL。,返回首次出现字符后的字符串
- 当输入self.的时候,会出现编译器的语法提示,原因是OBJ.PATH,因为这里的点,所以输入第二个参数时编辑器会给出正确的代码提示。
- 使用keypath(...)的时候前面会加上@符号,原因是经过keypath1(PATH)和keypath2(OBJ, PATH)之后出现的结果是一个C的字符串,前面加上@以后,就变成了OC的字符串了。
- ((void)(NO && ((void)OBJ.PATH, NO))是为了编译器可以出现语法提示,没有其他作用
用法:
// 例子1,一个参数的情况,会调用keypath1(PATH)
NSString *UTF8StringPath = @keypath(str.lowercaseString.UTF8String);
// 输出=> @"lowercaseString.UTF8String"
// 例子2,2个参数的情况,支持自省
NSString *versionPath = @keypath(NSObject, version);
// 输出=> @"version"
// 例子3,2个参数的情况
NSString *lowercaseStringPath = @keypath(NSString.new, lowercaseString);
// 输出=> @"lowercaseString"
集合类型的
#define collectionKeypath(...) \
metamacro_if_eq(3, metamacro_argcount(__VA_ARGS__))(collectionKeypath3(__VA_ARGS__))(collectionKeypath4(__VA_ARGS__))
#define collectionKeypath3(PATH, COLLECTION_OBJECT, COLLECTION_PATH) ([[NSString stringWithFormat:@"%s.%s",keypath(PATH), keypath(COLLECTION_OBJECT, COLLECTION_PATH)] UTF8String])
#define collectionKeypath4(OBJ, PATH, COLLECTION_OBJECT, COLLECTION_PATH) ([[NSString stringWithFormat:@"%s.%s",keypath(OBJ, PATH), keypath(COLLECTION_OBJECT, COLLECTION_PATH)] UTF8String]
3. RAC(TARGET, ...)
#define RAC(TARGET, ...) \
metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
(RAC_(TARGET, __VA_ARGS__, nil)) \
(RAC_(TARGET, __VA_ARGS__))
#define RAC_(TARGET, KEYPATH, NILVALUE) \
[[RACSubscriptingAssignmentTrampoline alloc] initWithTarget:(TARGET) nilValue:(NILVALUE)][@keypath(TARGET, KEYPATH)]
我们都知道RAC(TARGET, ...)宏是用来把一个信号绑定给一个对象的属性,绑定之后,每次信号发送出一个新的值,就会自动设定到执行的keypath中。当信号完成之后,这次绑定也会自动的解除。
RAC_(TARGET, KEYPATH, NILVALUE) 会把信号绑定到TARGET指定的KEYPATH上。如果信号发送了nil的值,那么会替换成NILVALUE赋值给对应的属性值上。
RAC_(TARGET, VA_ARGS)只不过是RAC_(TARGET, KEYPATH, NILVALUE)第三个参数为nil。
4. onExit
#define onExit \
rac_keywordify \
__strong rac_cleanupBlock_t metamacro_concat(rac_exitBlock_, __LINE__) __attribute__((cleanup(rac_executeCleanupBlock), unused)) = ^
@onExit定义当前代码段退出时要执行的一些代码。代码必须用大括号括起来并以分号结尾,无论是何种情况(包括出现异常,goto语句,return语句,break语句,continue语句)下跳出代码段,都会执行onExit后面的代码。
@onExit提供的代码被放进一个block块中,之后才会执行。因为在闭包中,所以它也必须遵循内存管理方面的规则。@onExit是以一种合理的方式提前退出清理块。
在相同代码段中如果有多个@onExit语句,那么他们是按照反字典序的顺序执行的。
@onExit语句不能在没有大括号的范围内使用。在实际使用过程中,这不是一个问题,因为@onExit后面如果没有大括号,那么它是一个无用的结构,不会有任何事情发生。