iOS

Magic Macro

Posted by Jincc on March 5, 2018

MAX

RAC(TARGET, …)

#define RAC(TARGET, ...) \
    metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
        (RAC_(TARGET, __VA_ARGS__, nil)) \
        (RAC_(TARGET, __VA_ARGS__))

这个宏有意思哈。它的意思是当argcount=1的时候,返回(RAC_(TARGET, __VA_ARGS__, nil)),否则返回(RAC_(TARGET, __VA_ARGS__)).这里有两个比较有意思的地方,第一个是metamacro_argcount(__VA_ARGS__)用来计算可变参数列表的长度,注意这是在预编译的时候.第二个是metamacro_if_eq这个用于判断的宏,它的实现没有判断语句,也没有条件运算符,那么它又是咋实现的呢?

metamacro_if_eq

// expands to true
metamacro_if_eq(0, 0)(true)(false)

// expands to false
metamacro_if_eq(0, 1)(true)(false)

This is primarily useful when dealing with indexes and counts in
 * metaprogramming.

它的含义为如果相等则返回第一个括号里面的值,否则返回后面的值.现在我们来试着展开这个宏:

NSString* eq =  metamacro_if_eq(0, 0)(@"1")(@"2");

看看metamacro_if_eq的定义:

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

上面的代码展开之后变成了metamacro_concat(metamacro_if_eq, 0)(0)(@"1")(@"2")

metamacro_concat是连接两个变量的意思:

再次展开以后metamacro_if_eq0(0)(@"1")(@"2") 再看看metamacro_if_eq0它是什么鬼?

#define metamacro_if_eq0(VALUE) \
    metamacro_concat(metamacro_if_eq0_, VALUE)

再次展开 metamacro_concat(metamacro_if_eq0_,0)(@"1")(@"2") metamacro_if_eq0_0(@"1")(@"2")

metamacro_if_eq0_0的定义:

#define metamacro_if_eq0_0(...) __VA_ARGS__ metamacro_consume_

再次展开 __VA_ARGS__ metamacro_consume_ (@"1") metamacro_consume_(@"2")

#define metamacro_consume_(...)

再次展开 (@"1") 卧槽,牛逼~.

metamacro_argcount

在预编译的时候计算一个可变参数的个数.我们大多在处理可变参数的时候都会在运行的时候用到va_start ,va_end来计算个数,其实我们写好代码以后,参数个数这个东西其实就已经确定了,这个宏就是用来在预处理的时候确定参数的个数,提供运行时的效率.

我们试着来展开一下下面的宏:

int count =  metamacro_argcount(a, b, c)

先看看它的定义:

#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(20, a,b,c, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)

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

展开以后metamacro_at20(a,b,c, 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__)

展开以后metamacro_head(3,2,1)

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

展开以后就变成了3.

这个实现真的很机智,先把20…1逆序拍好,然后在将可变参数插入到前面,然后截取掉前20个数,剩下的数中的头一个就是我们插入数的个数了.

metamacro_exprify

接受一个执行可变参数,可以是表达式,然后执行它,返回true.

#define metamacro_exprify(...) \
    ((__VA_ARGS__), true)
BOOL it = metamacro_exprify([self didReceiveMemoryWarning]);
//BOOL it = (([self didReceiveMemoryWarning]), 1);

metamacro_stringify

/**
 * Returns a string representation of VALUE after full macro expansion.
 */
#define metamacro_stringify(VALUE) \
        metamacro_stringify_(VALUE)
        
#define metamacro_stringify_(VALUE) # VALUE

#的意思是在VALUE的两边加上左右括号.

    NSString *s = @metamacro_stringify(2);
	//NSString *s = @"2";

metamacro_foreach

先看下它的定义: MACRO(index,value)将会被执行可变参数count次,然后通过SEP分离.

/**
 * For each consecutive variadic argument (up to twenty), MACRO is passed the
 * zero-based index of the current argument, CONTEXT, and then the argument
 * itself. The results of adjoining invocations of MACRO are then separated by
 * SEP.
 *
 * Inspired by P99: http://p99.gforge.inria.fr
 */
#define metamacro_foreach(MACRO, SEP, ...) \
        metamacro_foreach_cxt(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__)

我们来看看它的实现:

    #define loop(y,x) NSLog(@"%d-%@",y,x)
    metamacro_foreach(loop, ;, @"1",@"2",@"3");
    
    //    NSLog(@"%d-%@",0,@"1") ; NSLog(@"%d-%@",1,@"2") ; NSLog(@"%d-%@",2,@"3");

展开以后:metamacro_foreach_cxt(metamacro_foreach_iter,;,loop,@"1",@"2",@"3")

#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
        metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)

再次展开 metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(@"1",@"2",@"3"))(metamacro_foreach_iter, ;, loop, @"1",@"2",@"3") metamacro_foreach_cxt3(metamacro_foreach_iter, ;, loop, @"1",@"2",@"3")

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

这里就是循环的逻辑了.在metamacro_foreach_cxt2的后面拼接了最后依次loop的逻辑.

再来metamacro_foreach_cxt2(metamacro_foreach_iter,;,loop,@"1",@"2");metamacro_foreach_iter(2,loop,@"3") 再次metamacro_foreach_cxt1(metamacro_foreach_iter,;,loop,@"1");metamacro_foreach_iter(1,loop,@"2");metamacro_foreach_iter(2,loop,@"3") 再次metamacro_foreach_iter(0,loop,@"1");metamacro_foreach_iter(1,loop,@"2");metamacro_foreach_iter(2,loop,@"3")

#define metamacro_foreach_iter(INDEX, MACRO, ARG) MACRO(INDEX, ARG)

展开以后:loop(0,@"1");loop(1,@"2");loop(2,@"3") 最后:NSLog(@"%d-%@",0,@"1");NSLog(@"%d-%@",1,@"2");NSLog(@"%d-%@",2,@"3")

metamacro_take,metamacro_drop

/**
 * Returns the first N (up to twenty) variadic arguments as a new argument list.
 * At least N variadic arguments must be provided.
 */
#define metamacro_take(N, ...) \
        metamacro_concat(metamacro_take, N)(__VA_ARGS__)
 metamacro_take(3,1,2,3,4,5,6,7);
 //1,2,3

metamacro_is_even

/**
 * Returns 1 if N is an even number, or 0 otherwise. N must be between zero and
 * twenty, inclusive.
 *
 * For the purposes of this test, zero is considered even.
 */
#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)

输入一个数然判断该数是奇数还是偶数.这里我按照普通的思路写了另一个宏:

#define is_even(A) ({ __typeof__(A) __a = (A); (__a%2==0) ? 1:0 ;})
    
    bool isEven =  is_even(2);
    
    bool isEven2 =   metamacro_is_even(3);
    
    return isEven + isEven2;

预编译之后,它的结果为:

    _Bool isEven = ({ __typeof__(2) __a = (2); (__a%2==0) ? 1:0 ;});

    _Bool isEven2 = 0;

    return isEven + isEven2;

可以看出它们的区别,在优化等级=-Os的环境下,预编译过后metamacro_is_even 就已经得出了结果,但是is_even会直到运行的时候才得出结果.

这里也记录另一个点,当把优化等级改成-O3isEvenisEven2在汇编的时候都可以得出结果,这就是编译器的优化.下面是它的对比:

----o3
/*
     "-[ViewController bridge]":
     Lfunc_begin5:
     .loc    1 87 0                  @ /Users/Jincc/Documents/Github/Learning/ReactiveCocoa/ViewController.m:87:0
     @ BB#0:
     @DEBUG_VALUE: -[ViewController bridge]:self <- %R0
     @DEBUG_VALUE: -[ViewController bridge]:_cmd <- %R1
     @DEBUG_VALUE: -[ViewController bridge]:isEven <- 1
     @DEBUG_VALUE: -[ViewController bridge]:isEven2 <- 0
     .loc    1 96 5 prologue_end     @ /Users/Jincc/Documents/Github/Learning/ReactiveCocoa/ViewController.m:96:5
     movs    r0, #1 --------这里已经计算出来了
     bx    lr --------------返回
     Ltmp17:
     Lfunc_end5:
     */
    
    --------------os
    /*
     "-[ViewController bridge]":
     Lfunc_begin5:
     .loc    1 87 0                  @ /Users/Jincc/Documents/Github/Learning/ReactiveCocoa/ViewController.m:87:0
     @ BB#0:
     push    {r7, lr}
     mov    r7, sp
     sub    sp, #32
     movs    r2, #0
     movs    r3, #1
     movw    r9, #2
     str    r0, [sp, #28]
     str    r1, [sp, #24]
     Ltmp13:
     .loc    1 92 20 prologue_end    @ /Users/Jincc/Documents/Github/Learning/ReactiveCocoa/ViewController.m:92:20
     str.w    r9, [sp, #16]
     ldr    r0, [sp, #16]
     mov    r1, r9
     str    r2, [sp, #4]            @ 4-byte Spill
     str    r3, [sp]                @ 4-byte Spill
     bl    ___modsi3
     cmp    r0, #0
     movw    r0, #0
     it    eq
     moveq    r0, #1
     tst.w    r0, #1
     ldr    r0, [sp]                @ 4-byte Reload
     it    eq
     moveq    r0, #0
     str    r0, [sp, #12]
     ldr    r0, [sp, #12]
     Ltmp14:
     .loc    1 92 20 is_stmt 0       @ /Users/Jincc/Documents/Github/Learning/ReactiveCocoa/ViewController.m:92:20
     cmp    r0, #0
     movw    r0, #0
     it    ne
     movne    r0, #1
     .loc    1 92 10                 @ /Users/Jincc/Documents/Github/Learning/ReactiveCocoa/ViewController.m:92:10
     and    r0, r0, #1
     strb    r0, [r7, #-9]
     .loc    1 94 10 is_stmt 1       @ /Users/Jincc/Documents/Github/Learning/ReactiveCocoa/ViewController.m:94:10
     ldr    r0, [sp, #4]            @ 4-byte Reload
     strb    r0, [r7, #-21]
     .loc    1 96 12                 @ /Users/Jincc/Documents/Github/Learning/ReactiveCocoa/ViewController.m:96:12
     ldrb    r1, [r7, #-9]
     and    r1, r1, #1
     .loc    1 96 21 is_stmt 0       @ /Users/Jincc/Documents/Github/Learning/ReactiveCocoa/ViewController.m:96:21
     ldrb    r2, [r7, #-21]
     and    r2, r2, #1
     .loc    1 96 19                 @ /Users/Jincc/Documents/Github/Learning/ReactiveCocoa/ViewController.m:96:19
     add.w    r0, r1, r2
     .loc    1 96 5                  @ /Users/Jincc/Documents/Github/Learning/ReactiveCocoa/ViewController.m:96:5
     add    sp, #32  ----------还在执行add指令
     pop    {r7, pc} ----------返回
     Ltmp15:
     Lfunc_end5:
     */

keypath

#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))
    
	char *strchr(char* _Str,char _Ch)
	说明:返回首次出现_Val的位置的指针,返回的地址是被查找字符串指针开始的第一个与Val相同字符的指针,如果Str中不存在Val则返回NULL。