前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >RAC(ReactiveCocoa)介绍(十一)——RAC宏定义

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

作者头像
我只不过是出来写写代码
发布2019-04-02 14:59:03
2.6K0
发布2019-04-02 14:59:03
举报
文章被收录于专栏:我只不过是出来写写iOS

在编程领域里的宏是一种抽象(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所有的示例变量、属性以及方法。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018.06.03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档