RAC宏

2019-04-03  本文已影响0人  boy丿log

一、基础宏

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))
  1. 加void是为了不会出现警告
  2. 加NO是C语言判断条件短路表达式。增加NO && 以后,预编译的时候看见了NO,就会很快的跳过判断条件。
  3. strchr函数原型如下:
 extern char *strchr(const char *s,char c);
查找字符串s中首次出现字符c的位置。返回首次出现字符c的位置的指针,返回的地址是被查找   字符串指针开始的第一个与字符c相同字符的指针,如果字符串中不存在字符c则返回NULL。,返回首次出现字符后的字符串
  1. 当输入self.的时候,会出现编译器的语法提示,原因是OBJ.PATH,因为这里的点,所以输入第二个参数时编辑器会给出正确的代码提示。
  2. 使用keypath(...)的时候前面会加上@符号,原因是经过keypath1(PATH)和keypath2(OBJ, PATH)之后出现的结果是一个C的字符串,前面加上@以后,就变成了OC的字符串了。
  3. ((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后面如果没有大括号,那么它是一个无用的结构,不会有任何事情发生。

上一篇下一篇

猜你喜欢

热点阅读