平时我们在书写iOS代码的时候,很多时候是这样的。
UIView *view = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
view.backgroundColor = [UIColor redColor];
view.layer.cornerRadius = 10;
view.layer.borderColor = [UIColor greenColor].CGColor;
view.alpha = 0.7;
[self.view addSubview:view];
在这里首先创建了一个view
,然后对这个view
设置了诸多的属性:背景色、圆角、透明度
等。这是很简单的,可是写的久了之后,难免会觉得这有些繁琐,有些审美的疲劳(当然它可能本来就不美。。)。今天我们就一起学习一个非主流的、比较巧妙的小工具,这会让你的代码写起来更有意思,有一种耳目一新的赶脚。。
比如这样:
UIView *view = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
ml_css(view,
backgroundColor = [UIColor redColor],
layer.cornerRadius = 10,
layer.borderColor = [UIColor greenColor].CGColor,
alpha = 0.7
)
[self.view addSubview:view];
看到后是不是感觉:厉害了,word 哥,代码原来还可以这么写。它实现起来一定比较复杂吧,其实这个代码量加起来不到100行,并且关键的关键是这不到100行的代码全部都是在定义各种各样的宏
,所以对于学习宏是一个不错的素材。
闲话少说,先附上demo地址。
下载下来后,会看到一个MLCSSMacro.h
的文件,这里面是对RAC部分宏定义的封装。比如 :
-
metamacro_concat(A, B)
, 可以在编译的时候,将A和B合并到一起。 -
metamacro_argcount(...),
可以在编译的时候,获取到可变参数的个数。 -
metamacro_at(N, ...)
, 可以获取可变参数中第N个的值。 -
metamacro_inc(INDEX)
, 可以让INDEX的值+1。
可以说这个小工具是建立在RAC
宏的强大基础上的,关于对宏的一些基本概念还不太清楚的,可以看我的上一篇文章,宏的奇妙漂流,那里面讲述了宏的基本语法,并举了两个例子(其中就包括metamacro_argcount
宏)一步步的对宏进行展开。
下面我们就开始具体分析是怎么通过宏定义实现这种奇妙的效果的。其中demo中有一个不太亲切的的地方,那就是注释少的可怜,对想要学习的人是一种阻碍。笔者也是看了好久再加上跟它的原作者做了些沟通后,才算是解除了一些困惑。
先看下源代码。
#ifndef MLCSSMacro_h
#define MLCSSMacro_h
#define ml_css(OBJ, ...) ml_css_(OBJ, __VA_ARGS__)
#define ml_css_(OBJ, ...) metamacro_concat(ml_css_, metamacro_argcount(__VA_ARGS__))(0, OBJ, __VA_ARGS__)
#define ml_css_0(INDEX, OBJ, ...)
#define ml_css_1(INDEX, OBJ, ...) OBJ.metamacro_at(INDEX, __VA_ARGS__);\
ml_css_0(metamacro_inc(INDEX), OBJ, __VA_ARGS__)
#define ml_css_2(INDEX, OBJ, ...) OBJ.metamacro_at(INDEX, __VA_ARGS__);\
ml_css_1(metamacro_inc(INDEX), OBJ, __VA_ARGS__)
·····
·····
·····
·····
#define ml_css_20(INDEX, OBJ, ...) OBJ.metamacro_at(INDEX, __VA_ARGS__);\
ml_css_19(metamacro_inc(INDEX), OBJ, __VA_ARGS__)
#endif /* ML
这里只是贴出了部分的代码,我们先来分析ml_css(OBJ, ...)
这个宏,这个宏的第一个参数接收一个对象,然后后面的...
号代表的是可变参数。也就是说后面的参数可以有很多,只是在代码中做了限制,最多20个。
看ml_css(OBJ, ...)
的实现ml_css_(OBJ, __VA_ARGS__)
,比如在这里我输入的是
ml_css(view,
backgroundColor = [UIColor redColor]),
layer.cornerRadius = 10
那么view
就是那个OBJ
,而view
的那两个属性就是可变参数__VA_ARGS__
了。
可以将它们俩作为一个整体在宏里面传递。宏继续往下展开,我们看到了下面的宏。
metamacro_concat(ml_css_, metamacro_argcount(__VA_ARGS__))(0, OBJ, __VA_ARGS__)
如果你看过上一篇文章的话,那么你会很容易的看出这是什么意思。先看metamacro_argcount(__VA_ARGS__)
,这个宏接受一个可变参数,并能得到参数的个数,比如我们传入那两个属性,那这个宏处理后结果就是2,再看外面的metamacro_concat(A,B)
宏,我们说过了,这个宏会将A和B合并到一起,所以A就是括号中的第一项:ml_css_
,B就是刚刚得到的2,合到一起后我们得到了ml_css_2
这个宏,而这个宏是在下面有定义过得:ml_css_2(INDEX, OBJ, ...)
宏,它接收一个INDEX
变量、一个OBJ对象和一个可变参数。带入参数后我们就得到了ml_css_2(0,OBJ, __VA_ARGS__)
。
再看下ml_css_2
宏的展开
#define ml_css_2(INDEX, OBJ, ...) OBJ.metamacro_at(INDEX, __VA_ARGS__);\
ml_css_1(metamacro_inc(INDEX), OBJ, __VA_ARGS__)
这里的INDEX
是0
,OBJ
就是我们的view
, __VA_ARGS__
就是那两个可变参数。
看下这个宏第一步做的什么事。
OBJ.metamacro_at(INDEX, __VA_ARGS__);
首先是从参数列表中获取到第INDEX
个参数, 然后对OBJ
做了点语法
操作。相当于执行了一个view.param
操作,虽然我们没有明确的写,但是Xcode会给出正确的相关提示。比如我输入的b
,那么Xcode就会给出下列的提示:
再看下普通写法下的提示
可以看到这两种是一模一样的,因为我们在宏中对对象做了点语法
的操作,编译器就会自动的帮我们做提示的这件事情,当然输入一个不存在的属性也是会报错的。
你可以自己写一个下面的宏,然后按照对它设置一个属性,当然在这里只能设置一个。
#define sk_css(OBJ,...) OBJ.metamacro_at(0, __VA_ARGS__);
再回到刚才宏执行的地方,进入下一步。ml_css_1(metamacro_inc(INDEX), OBJ, __VA_ARGS__)
,从这里看到对INDEX
变量做了一个+1的操作,从0变成了1,然后将参数传递给了ml_css_1
宏,
#define ml_css_1(INDEX, OBJ, ...) OBJ.metamacro_at(INDEX, __VA_ARGS__);\
ml_css_0(metamacro_inc(INDEX), OBJ, __VA_ARGS__)
其他的都是一样的,只是INDEX
变成了1,所以这时候会从可变参数列表中,取出下一个参数来,比如我们输入的第二个参数是layer
,那么就会执行view.layer
这个set方法。而这个执行完后,会来到ml_css_0(INDEX, OBJ, ...)
这个宏,这个宏其实是没有任何实际意义的,只是作者为了格式统一起来而加上去的。。
所以可以看出,如果是有n个属性的话,那么会从ml_css_n
所对应的宏开始执行,执行完后INDEX
会依次加1,然后将参数再传递给ml_css_(n-1)
宏进行执行,是倒着往前执行,直到ml_css_0
为止。
以上就是我们对这个巧妙地小工具的剖析,主要目的并不是说要鼓励大家这样写代码,因为这可能会被以后维护的人给骂死,我们主要是通过这个来感受一下:宏,原来还可以这样用。
其实当我们对代码稍加一点改变比如把OBJ.metamacro_at(INDEX, __VA_ARGS__);
改成[OBJ metamacro_at(INDEX, __VA_ARGS__)];
的话,那么我们就可以对方法也进行这种格式的书写了,比如这样:
ml_css(btn,
setFrame:CGRectMake(100, 100, 100, 100),
setTitle:@"test" forState:UIControlStateNormal,
setBackgroundColor:[UIColor redColor],
addTarget:self action:@selector(test:) forControlEvents:UIControlEventTouchUpInside
)
笔者也在思考该怎样才能让属性跟方法同时可以在一个宏中进行设置,只是暂时遇到了一些困难,如果有好的意见的话,可以留言,那样我们就能够比较爽的撸代码了。。
最后切记一句话,小试怡情,大用伤身啊。
附上demo地址。