背景
我们都用过Masonry等三方库,其链式变成的形式使代码更简洁、更语义化的表达自己的逻辑。这种用于特定领域的表达方式叫做DSL(Domain Specific Language),本文就介绍一下DSL的实现思路。
举个例子我们来创建一个UIView,设置其frame、背景色,并添加至父View上,是这个样子的。
UIView * aView = [[UIView alloc]initWithFrame: aFrame];
aView.backgroundColor = aColor;
[self.view addSubview:aView];
再看下链式编程的表达式,
UIView *view = AllocA(UIView).with.position(20,20).size(50,50).bgColor(UIColor.redColor).intoView(self.view);
语义分析
链式变成从语义上更像我们平常的句子。包含宾语(动作接收者)、助词(起连贯作用)、定语(修饰词)。当然要实现上面的形式必须借助一个中间类,假定为ViewMaker。
语义分析-3.png
实现
关键字:block、宏、中间类。
实现DLS、第一步就遇到麻烦了,OC中调用方法都需要用中括号表示,如何不用中括号去调用方法?
答案是在类外部声明,并实现,这个类不依存于类对象,所以无法调用self。在该方法中创建了viewMaker对象,并将所类型保存至viewMaker对象中。
ViewMaker * alloc_a(Class aClass){
ViewMaker * maker = ViewMaker.new;
maker.viewClass = aClass;
return maker;
}
这样我们可以实现直接调用
alloc_a([UIView class]);
但是我们必须传入一个类对象[UIView class]的形式,又用到了中括号,这里可以用神奇的宏解决这个问题
//根据宏的对应关系,只需要直接传类型即可,不用传类对象。
#define AllocA(aClass) alloc_a([aClass class]);
这里我们用很简洁的形式传入了宾语,并保存至中间对象中,返回用作进一步操作。
AllocA(UIView);
为了语法连贯需要一些助词来帮助,这里只是为了语义连贯。返回中间类自身就可以。
//通过get方法返回
- (ViewMaker *)with{
return self;
}
//调用
AllocA(UIView).with
定语也就是修饰词用block来实现,这里传参过程用block来实现,用属性形式保存在viewMaker对象中,因为要实现链式变成,所以要把viewMaker对象返回回来,这样修饰词也保存完毕。
@property (nonatomic, assign) CGPoint myPosition;
@property (nonatomic, assign) CGSize mySize;
@property (nonatomic, strong) UIColor *color;
@property (nonatomic, copy) ViewMaker * (^position)(CGFloat x, CGFloat y);
@property (nonatomic, copy) ViewMaker * (^size)(CGFloat width, CGFloat height);
@property (nonatomic, copy) ViewMaker * (^bgColor)(UIColor *color);
- (instancetype)init{
if (self = [super init]){
__weak __typeof(self) wSelf = self;
_position = ^ViewMaker * (CGFloat x, CGFloat y){
wSelf.myPosition = CGPointMake(x, y);
return wSelf;
};
_size = ^ViewMaker * (CGFloat width, CGFloat height){
wSelf.mySize = CGSizeMake(width, height);
return wSelf;
};
_bgColor = ^ViewMaker * (UIColor *color){
wSelf.color = color;
return wSelf;
};
return self;
}
终结词:这个词在现代语法中是找不到的,但是在DSL中是很重要的,ViewMaker的实例从头到收集了很多的修饰,在最后的表达式中产生了结果,本例中就是根据类型创建对象,根据修饰属性给对象初始化赋值,并返回。
代码
//.h文件
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#define AllocA(aClass) alloc_a([aClass class])
@interface ViewMaker : NSObject
@property (nonatomic, readonly) ViewMaker * with;
@property (nonatomic, strong) Class viewClass;
@property (nonatomic, copy) ViewMaker * (^position)(CGFloat x, CGFloat y);
@property (nonatomic, copy) ViewMaker * (^size)(CGFloat width, CGFloat height);
@property (nonatomic, copy) ViewMaker * (^bgColor)(UIColor *color);
@property (nonatomic, copy) UIView * (^intoView)(UIView *superView);
@end
ViewMaker * alloc_a(Class aClass);
//.m文件
#import "ViewMaker.h"
@interface ViewMaker()
@property (nonatomic, assign) CGPoint myPosition;
@property (nonatomic, assign) CGSize mySize;
@property (nonatomic, strong) UIColor *color;
@end
@implementation ViewMaker
- (instancetype)init{
if (self = [super init]){
__weak __typeof(self) wSelf = self;
_position = ^ViewMaker * (CGFloat x, CGFloat y){
wSelf.myPosition = CGPointMake(x, y);
return wSelf;
};
_size = ^ViewMaker * (CGFloat width, CGFloat height){
wSelf.mySize = CGSizeMake(width, height);
return wSelf;
};
_bgColor = ^ViewMaker * (UIColor *color){
wSelf.color = color;
return wSelf;
};
_intoView = ^UIView * (UIView * superView){
CGRect rect = CGRectMake(wSelf.myPosition.x, wSelf.myPosition.y, wSelf.mySize.width, wSelf.mySize.height);
viewClass * view = [[viewClass alloc] initWithFrame:rect];
view.backgroundColor = wSelf.color;
[superView addSubview:view];
return view;
};
}
return self;
}
- (ViewMaker *)with{
return self;
}
@end
ViewMaker * alloc_a(Class aClass){
ViewMaker * maker = ViewMaker.new;
maker.viewClass = aClass;
return maker;
}
//调用
UIView *view = AllocA(UIView).with.position(20,20).size(50,50).bgColor(UIColor.redColor).intoView(self.view);
总结
通过类外函数创建中间类,通过中间类的block属性传入参数保存并返回中间类,最终通过终结词返回最终创建的对象。这条链的传递的是中间类,最终通过中间类保存的描述信息返回最终对象。
臧成威老师博客
网友评论