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

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

打破实例变量的循环引用

KVO属性监听

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

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

#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__)代码实现:

#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(...),这是在预编译时用来获取传入参数的个数。
通过查看层层宏定义封装,依次可找到下列宏

#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的宏定义

#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__),可以发现下列宏定义

#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(...)宏展开后变为:

//宏里的可变参数个数为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的宏定义为

#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

#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_(...)宏,那么来看下这个宏什么作用

#define rac_weakify_(INDEX, CONTEXT, VAR) \
    CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);

展开后

__weak __typeof__(self)  self_weak_ = (self);
__weak __typeof__(str)  str_weak_ = (str);

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




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

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

#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)

#define _RACObserve(TARGET, KEYPATH) \
({ \
    __weak id target_ = (TARGET); \
    [target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \
})

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

#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所有的示例变量、属性以及方法。


该文章首次发表在 简书:我只不过是出来写写代码 博客,并自动同步至 腾讯云:我只不过是出来写写iOS 博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容