objective-c DSL的实现思路

背景

我们都用过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属性传入参数保存并返回中间类,最终通过终结词返回最终创建的对象。这条链的传递的是中间类,最终通过中间类保存的描述信息返回最终对象。
臧成威老师博客

推荐阅读更多精彩内容

  • pdf下载地址:Java面试宝典 第一章内容介绍 20 第二章JavaSE基础 21 一、Java面向对象 21 ...
    王震阳阅读 83,572评论 26 524
  • 同事丫丫是我的益友,我们一直很同频,经常交流工作学习心得。 某个午后,我们相约在万达光阴里酒吧,寻一处安静角落,点...
    陶心暖暖阅读 47评论 0 3
  • 好久没有联系了,因为不敢联系,我怕,怕准备了一大堆话,最后都憋下去了,我怕突然冷场,怕不知道说啥。 自从那天你和我...
    二小货阅读 107评论 2 4
  • 在蕾丝控,雪纺控充斥的服装界,相信一定还有那么一些人,是民族风控,一如我自己。 有时候我也想我自己,似乎对我们祖先...
    舒眉弯阅读 89评论 0 0
  • 写下这个标题,总想起高中那个年轻的语文老师在课堂上告诉我们,他已经开始写他的父亲了,希望写完能够发表。于是我们的高...
    wen_a阅读 71评论 2 2