iOSReactiveCocoaRACMacro

ReactiveCocoa 中 奇妙无比的“宏”魔法

前言

在ReactiveCocoa 中,开源库作者为我们提供了很多种魔法,“黑”魔法,“红”魔法……今天就让先来看看“红”魔法。

在ReactiveCocoa 中,封装了很多非常实用的“宏”,使用这些“宏”为我们开发带来了很多的便利。

今天就来盘点一下RAC中的宏是如何实现的。

目录

  • 1.关于宏
  • 2.ReactiveCocoa 中的元宏
  • 3.ReactiveCocoa 中常用的宏

一. 关于宏

(Macro),是一种批量处理的称谓。

在编程领域里的宏是一种抽象(Abstraction),它根据一系列预定义的规则替换一定的文本模式。解释器编译器在遇到宏时会自动进行这一模式替换。绝大多数情况下,“宏”这个词的使用暗示着将小命令或动作转化为一系列指令。

宏的用途在于自动化频繁使用的序列或者是获得一种更强大的抽象能力。
计算机语言如C语言汇编语言有简单的宏系统,由编译器汇编器的预处理器实现。C语言的宏预处理器的工作只是简单的文本搜索和替换,使用附加的文本处理语言如M4,C程序员可以获得更精巧的宏。

Lisp类语言如Common LispScheme有更精巧的宏系统:宏的行为如同是函数对自身程序文本的变形,并且可以应用全部语言来表达这种变形。一个C宏可以定义一段语法的替换,然而一个Lisp的宏却可以控制一节代码的计算。

对于编译语言来说,所有的宏都是在预编译的时候被展开的,所以在lex进行词法扫描生成Token,词法分析过程之前,所有的宏都已经被展开完成了。

对于Xcode,预处理或者预编译阶段是可以直接查看的。

随便写一个宏,然后打开Xcode右上方的Assistant,选择“Preprocess”就可以看到该文件预处理之后的样子了。可以看到左边的@weakify(self) 被转换成了右边的两行代码了。

关于这个Xcode的这个功能还有2点补充说明:

1.不同阶段的Preprocessed可能不同,要根据你的目标去选择预处理的条件。

比如这里就有5种预编译的种类可以选择。

2.宏经过预编译之后出来的代码,是可以用来检测宏写的是否正确的,但是无法看到宏被展开的具体过程。这意味着我们可以通过Xcode这个功能来查看宏的作用,但是无法知道宏的具体实现。具体实现还是需要通过查看源码来分析。

ReactiveCocoa中的宏,如果不查看源码分析,会觉得那些宏都像魔法一样奇妙无比,接下来就来解开“宏”魔法的神秘面纱。

二. ReactiveCocoa 中的元宏

在ReactiveCocoa的宏中,作者定义了这么一些基础的宏,作为“元宏”,它们是构成之后复杂宏的基础。在分析常用宏之前,必须要先分析清楚这些元宏的具体实现。

1. metamacro_stringify(VALUE)


#define metamacro_stringify(VALUE) \
        metamacro_stringify_(VALUE)

#define metamacro_stringify_(VALUE) # VALUE

metamacro_stringify( )这个宏用到了#的用法。#在宏中代表把宏的参数变为一个字符串。这个宏的目的和它的名字一样明显,把入参VALUE转换成一个字符串返回。

这里可能就有人有疑问,为啥要包装一层,不能直接写成下面这样:


#define metamacro_stringify(VALUE)  # VALUE


语意确实也没有变,但是有种特殊情况下就会出现问题。

举个例子:


#define NUMBER   10
#define ADD(a,b) (a+b)
NSLog(@"%d+%d=%d",NUMBER, NUMBER, ADD(NUMBER,NUMBER));

输出如下:


10+10=20

这样子确实是没有问题,但是稍作修改就会有问题。


#define STRINGIFY(S) #S
#define CALCULATE(A,B)  (A##10##B)

NSLog(@"int max: %s",STRINGIFY(INT_MAX));
NSLog(@"%d", CALCULATE(NUMBER,NUMBER));

如果是这种情况下,第二个NSLog打印是会编译错误的。上面两句经过预编译之后,宏会被展开成下面这个样子:


NSLog(@"int max: %s","INT_MAX");
NSLog(@"%d", (NUMBER10NUMBER));

可以发现,宏并没有再次被展开。解决办法也很简单,就是把宏包装一层,写一个转接宏出来。


#define CALCULATE(A,B)   _CALCULATE(A,B)   // 转换宏
#define _CALCULATE(A,B)  A##10##B

再次测试一下,这里我们使用官方的metamacro_stringify


NSLog(@"int max: %s",metamacro_stringify(INT_MAX));
NSLog(@"%d", CALCULATE(NUMBER,NUMBER));

这样最终打印出来的结果和我们想要的一致,没有问题。


2147483647
101010

CALCULATE(NUMBER,NUMBER) 第一层转换成 _CALCULATE(10,10),接着第二次转换成10##10##10,也就是101010。

当然这里是2层转换,如果有多层转换就需要更多个转换宏了。


NSLog(@"%d", CALCULATE(STRINGIFY(NUMBER),STRINGIFY(NUMBER)));


上面这个例子就是3层了,按照之前我们的写法还是编译报错。如果是超过2,3层的多层的情况,就该考虑考虑宏设计的语意的问题,尽量不让使用者产生错误的用法。

2. metamacro_concat(A, B)


#define metamacro_concat(A, B) \
        metamacro_concat_(A, B)
#define metamacro_concat_(A, B) A ## B

这个宏就是用来合并入参A,B到一起。在RAC里面主要用这个方法来合成另外一个宏的名字。

3. metamacro_argcount(...) 和 metamacro_at(N, ...)

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)


这里会调用metamacro_at(N, ...)宏。


#define metamacro_at(N, ...) \
        metamacro_concat(metamacro_at, N)(__VA_ARGS__)

把这个宏展开,于是得到:


#define metamacro_at(N, ...) \
        metamacro_atN(__VA_ARGS__)

于是通过metamacro_concat合成命令,就得到了一连串的metamacro_atN宏命令:


#define metamacro_at0(...) metamacro_head(__VA_ARGS__)
#define metamacro_at1(_0, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at2(_0, _1, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at3(_0, _1, _2, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at4(_0, _1, _2, _3, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at5(_0, _1, _2, _3, _4, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at6(_0, _1, _2, _3, _4, _5, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at7(_0, _1, _2, _3, _4, _5, _6, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at8(_0, _1, _2, _3, _4, _5, _6, _7, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at9(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, ...) metamacro_head(__VA_ARGS__)
#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__)



可见N的取值只能从0到20。


#define metamacro_head(...) \
        metamacro_head_(__VA_ARGS__, 0)
#define metamacro_head_(FIRST, ...) FIRST

metamacro_head展开之后变成:


#define metamacro_head(FIRST,..., 0)  FIRST

metamacro_head的意图就很明显,是用来获取后面可变入参的第一个参数。

回到metamacro_atN宏上面来,那么把它展开就是下面这样:


#define metamacro_atN(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ... , _N, ...) metamacro_head(__VA_ARGS__)


当然,N的取值还是从0到20,那么metamacro_atN宏获取到的值就是可变参数列表里面的第N个参数值。参数从0开始。

再回到最初的metamacro_argcount(...)宏,目前展开到这一步:



#define metamacro_argcount(...) \
        metamacro_at20(__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)


由于__VA_ARGS__个数不能超过20个,所以必定是在0-19之间。


metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ..., 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1) metamacro_head(__VA_ARGS__)


假设入参是有5个:


metamacro_argcount(@"1",@"2",@"3",@"4",@"5");

先把5个参数放入metamacro_at20的前五个位置。然后从第6个位置开始倒序插入20-1的数字。如下图:

我们可以把倒序的数字想象成一把尺子,是用来衡量或者指示当前有多少个参数的。尺子的最左边对齐上面20个空位的第一位,尺子后面多出来的部分,取出来,然后进行metamacro_head操作,取出第一位参数,这个数字就是整个参数的个数了。这把虚拟的“尺子”是会左右对齐的,具体的位置就要根据填入参数的个数来决定的。

这个宏的原理也很简单,20 - ( 20 - n )= n。metamacro_argcount(...) 宏就是这样在预编译时期获取到参数个数的。

作者也标明了,这个宏的设计灵感来自于P99神库,有兴趣的同学可以去看看这个库。

4. metamacro_foreach(MACRO, SEP, ...) 和 metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)

先来分析分析metamacro_foreach(MACRO, SEP, ...) 宏:


#define metamacro_foreach(MACRO, SEP, ...) \
        metamacro_foreach_cxt(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__)

看到定义就知道metamacro_foreach(MACRO, SEP, ...) 和 metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) 是一样的作用。前者只不过比后者少了一个foreach的迭代子。

1. metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)宏

再来看看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(MACRO, SEP, ...)宏就可以等价于


metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__)

回到metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...)宏的展开表达式上面来,假设__VA_ARGS__的参数个数为N。

metamacro_concat 宏 和 metamacro_argcount 宏上面介绍过了,那么可以继续把宏展开成下面的样子:


metamacro_foreach_cxtN(MACRO, SEP, CONTEXT, __VA_ARGS__)

这里又是利用metamacro_concat 宏动态的合并成了另一个宏的例子。


#define metamacro_foreach_cxt0(MACRO, SEP, CONTEXT)
#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)

#define metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \
    metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) \
    SEP \
    MACRO(1, CONTEXT, _1)

#define metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \
    metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \
    SEP \
    MACRO(2, CONTEXT, _2)

#define metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \
    metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \
    SEP \
    MACRO(3, CONTEXT, _3)

#define metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \
    metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \
    SEP \
    MACRO(4, CONTEXT, _4)

#define metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \
    metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \
    SEP \
    MACRO(5, CONTEXT, _5)

#define metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \
    metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \
    SEP \
    MACRO(6, CONTEXT, _6)

#define metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \
    metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \
    SEP \
    MACRO(7, CONTEXT, _7)

#define metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \
    metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \
    SEP \
    MACRO(8, CONTEXT, _8)

#define metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
    metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \
    SEP \
    MACRO(9, CONTEXT, _9)

#define metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
    metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
    SEP \
    MACRO(10, CONTEXT, _10)

#define metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
    metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
    SEP \
    MACRO(11, CONTEXT, _11)

#define metamacro_foreach_cxt13(MACRO, SEP,