在程序开发中,我们总是希望能够更加简洁、更加语义化地去表达自己的逻辑,链式调用是一种常见的处理方式。我们常用的 Masonry、 Expecta 等第三方库就采用了这种处理方式。
像这种用于特定领域的表达方式,我们叫做 DSL (Domain Specific Language),本文就介绍一下如何实现一个链式调用的 DSL.
我们举一个具体的例子,比如我们用链式表达式来创建一个 UIView,设置其 frame、backgroundColor, 并添加至某个父 View。
对于最基本的 Objective-C (在 iOS4 block 出现之前),如果要实现链式调用,只能是这个样子的:
有了 block,我们可以把中括号的这种写法改为点语法的形式
可以看出,链式语法的语义性很明确,后者的语法更加紧凑,下面我们从两个角度看一下后者的实现。
链式调用可以用两种方式来实现:
1.在返回值中使用属性来保存方法中的信息
比如,Masonry 中的
.left .right .top .bottom
等方法,调用时会返回一个
MASConstraintMaker
类的实例,里面有
left/right/top/bottom
等属性来保存每次调用时的信息;
再比如,Expecta 中的方法
.notTo
方法会返回一个
EXPExpect
类的实例,里面有个 BOOL 属性
self.negative
来记录是否调用了
.notTo
;
再比如,上例中的 .with 方法,我们可以直接
return self;
2.使用 block 类型的属性来接受参数
比如 Masonry 中的
.offset(15)
方法,接收一个 CGFloat 作为参数,可以在
MASConstraintMaker
类中添加一个 block 类型的属性:
比如例子中的
.position(x, y)
,可以给的某类中添加一个属性:
在调用
.position(x, y)
方法时,执行这个block,返回 ViewMaker 的实例保证链式调用得以进行。
从语义层面上,需要界定哪些是助词,哪些是需要接受参数的。为了保证链式调用能够完成,需要考虑传入什么,返回什么。
还是以上面的例子来讲:
分步来看一下,这个 DSL 表达式需要描述的是一个祈使句,以 Alloc 开始,以 intoView 截止。在 intoView 终结语之前,我们对 UIView 进行一定的修饰,利用
position
size
bgColor
这些。
下面我们分别从四段来看,如何实现这样一个表达式:
在 AllocA(UIView) 的语义中,我们确定了宾语是 a UIVIew。由于确定 UIView 是在 intoView 截止那时,所以我们需要创建一个中间类来保存所有的中间条件,这里我们用 ViewMaker 类。
另外我们可以注意到AllocA是一个函数,而UIView无法直接传递到这个函数中,语法就要变成
AllocA([UIView class])
而失去了简洁性。所以我们需要先定义一个宏来“吞”掉中括号和
class
这个方法: