RAC(ReactiveCocoa)介绍(十一)——RAC宏定义

2019-04-02 14:59:03 浏览数 (1)

在编程领域里的宏是一种抽象(Abstraction),它根据一系列预定义的规则替换一定的文本模式。解释器在遇到宏时会自动进行这一模式替换。绝大多数情况下,“宏”这个词的使用暗示着将小命令或动作转化为一系列指令。 在RAC框架中,其宏定义的功能强大能帮助开发者更加快速、便捷地进行开发工作。常用的比如:打破循环引用、以及KVO方法的属性监听等等。

打破实例变量的循环引用

KVO属性监听

这一篇主要探究RAC中的宏定义强大之处究竟在哪。 首先来看下最常用的@weakify(self)

weakify(...)实现

此处注意,反斜杠的作用是作为连接符使用,将代码进行连接。即使用weakify(...)宏定义时,将先后执行 rac_keywordifymetamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__) 代码。 先来看下rac_keywordify代码的作用:

代码语言:javascript复制
#if DEBUG
#define rac_keywordify autoreleasepool {}
#else
#define rac_keywordify try {} @catch (...) {}
#endif

在debug环境下,只有一句autoreleasepool {},此代码是增强代码的编译能力,至于为何要如此使用?在经常使用的宏定义RACObserve(TARGET, KEYPATH)观察KVO属性时,能够在KEYPATH中,代码预提示出指定TARGET中的属性

RACObserve能够提示出当前self中存在的实例变量

metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)代码实现:

代码语言:javascript复制
#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)  
 metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)
//此函数中,分别将rac_weakify_传入MACRO参数,(空格)传入SEP,__weak传入CONTEXT,__VA_ARGS_(可变参数)传入...

metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)函数中,实现了metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)

首先来看看第二个参数metamacro_argcount(...),这是在预编译时用来获取传入参数的个数。 通过查看层层宏定义封装,依次可找到下列宏

代码语言:javascript复制
#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_at(N, ...) 
//此处将20传入N参数中,其余的作为可变参数传入
        metamacro_concat(metamacro_at, N)(__VA_ARGS__)
#define metamacro_concat(A, B) 
        metamacro_concat_(A, B)
#define metamacro_concat_(A, B) A ## B

在Objective-C语言中方法调用底层实际上是对C的消息转发,Objective-C语言最终要编译成C函数语言,接触过runtime之后会更加明白。比如在runtime中最常用的objc_msgSend方法,Objective-C函数调用都要通过它来进行消息发送实现,objc_msgSend(id self, SEL _cmd, arg),在Objective-C中,该消息发送方法默认实现了id类型的self以及方法选择器_cmd,arg参数为开发者自定义的内容。

宏定义在Objective-C中使用#define,在C中使用define,而#是Objective-C区别于C语言的存在。那么#define metamacro_concat_(A, B) A ## B从Objective-C环境编译为C语言时,最终实现的是AB,也就意味着将A、B拼接到一起。上述宏定义最终是将metamacro_at参数与20参数拼接到一起,组成metamacro_at20(__VA_ARGS__)。拼接完成之后可以找到metamacro_at20的宏定义

代码语言:javascript复制
#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__)

而且此处会发现,从0-20都已存在宏定义

metamacro_atN宏定义

metamacro_atN的宏定义,意思为截取掉宏定义中前N个元素,保留剩下的元素传入至metamacro_head(__VA_ARGS__)中 展开metamacro_head(__VA_ARGS__),可以发现下列宏定义

代码语言:javascript复制
#define metamacro_head(...) 
        metamacro_head_(__VA_ARGS__, 0)
#define metamacro_head_(FIRST, ...) FIRST

意味着要取截取后剩下元素中的第一个元素,而这个元素的值也就是metamacro_argcount(...)宏返回出来的元素个数。 其实现原理为20 -( 20 - n )= n,metamacro_argcount(...) 宏就是这样在预编译时期获取到参数个数的。

为了更方便理解metamacro_argcount(...) 宏实现的过程,举个例子: metamacro_argcount(self,(NSString *)str)计算出个数为2的过程。 metamacro_argcount(...)宏展开后变为:

代码语言:javascript复制
//宏里的可变参数个数为22个
metamacro_at(20, self, str, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
//合并后变为metamacro_at20(...)宏
metamacro_at20(self, str, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

而metamacro_at20的宏定义为

代码语言:javascript复制
#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__)
//metamacro_head(...)的可变参数即为截取后剩下的2个元素(2,1)
metamacro_head(2,1)

在metamacro_at20宏中,前20个元素位置已被预设好的元素占用,那么metamacro_head(...)的可变参数即为截取后剩下的2个元素(2,1)。通过metamacro_head宏取出第一个元素的值并返回,最后得到的数值为2,传入参数的个数为2。这也就是在预编译时如何获取传参个数的全过程。

这时,再回到metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)宏,现在已经知道metamacro_concat是用来生成一个新的宏定义,此处就变为metamacro_foreach_cxtN(MACRO, SEP, CONTEXT, __VA_ARGS__) 注:此处cxtN中的N为metamacro_argcount(...)宏返回的个数。

metamacro_foreach_cxtN(MACRO, SEP, CONTEXT, __VA_ARGS__)宏的实现,N同样为0-20

继续拿上面的例子来说,当返回为2个元素个数之后 在宏最外部分别将rac_weakify_传入MACRO参数,(空格)传入SEP,__weak传入CONTEXT

代码语言:javascript复制
#define metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) 
    metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) 
    SEP 
    MACRO(1, CONTEXT, _1)
    //展开后变为
    rac_weakify_(0, __weak, self)  
    rac_weakify_(1, __weak, str)

此时,得到了一个rac_weakify_(...)宏,那么来看下这个宏什么作用

代码语言:javascript复制
#define rac_weakify_(INDEX, CONTEXT, VAR) 
    CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);

展开后

代码语言:javascript复制
__weak __typeof__(self)  self_weak_ = (self);
__weak __typeof__(str)  str_weak_ = (str);

。。。。。。。。。分析了这么一大圈,就是为了一句弱引用???

不过这样也有好处,就是可以最多传入20个对象全部弱引用

既然已经分析出了@weakify(...)宏定义的作用,那么@strongify(...)宏定义作用也就显而易见了:将对象变为强引用。

代码语言:javascript复制
#define rac_strongify_(INDEX, VAR) 
    __strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);
//最后的展开结果
__weak __typeof__(self_weak_)  self = (self_weak_);
__weak __typeof__(str_weak_)  str = (str_weak_);

这也说明了在RAC中@weakify(...)宏定义与@strongify(...)一定要搭配使用的原因。 为什么要在这里加一个@符号? Objective-C源于C语言,输入字符串时,C语言用""来表示,而Objective-C是用@""来表示。此处要加@符号,是把C语言的结构包装成Objective-C。添加@符号,作用为预编译,会从底层C语言中找相应的函数,寻找TARGET相应的KEYPATH路径。

接下来,来分析一下RAC中观察属性KVO宏定义RACObserve(TARGET, KEYPATH)

代码语言:javascript复制
#define _RACObserve(TARGET, KEYPATH) 
({ 
    __weak id target_ = (TARGET); 
    [target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; 
})

将TARGET弱引用生成一个target_对象,下面主要来分析下一句代码 先查看@keypath(TARGET, KEYPATH)

代码语言:javascript复制
#define keypath(...) 
//判断可变参数个数是否为1
//若为1则执行(keypath1(__VA_ARGS__))
//否则执行(keypath2(__VA_ARGS__))
    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))

此处@keypath(TARGET, KEYPATH)一定要添加@符号,就是为了能预编译出TARGET中所有的KEYPATH属性。 何为预编译? 在main函数执行之前,执行预编译处理。把整个类加载进内存中,在编程过程中,会去匹配TARGET类的类型,当匹配到对应类之后,会去编译查找对应的属性表property list、成员表IRG list、方法表method list。所以这里执行了预编译处理后,就可以提示出TARGET所有的示例变量、属性以及方法。

0 人点赞