使用DSL简化您的Objective-C代码

当我第一次学习iOS编程时,我在Objective-C语法上苦苦挣扎。 但是一旦我适应了自己,它就会成为我最喜欢的语言之一。 它包含了很多哲学! 但是,由于Objc具有自己的样式,因此有时代码写得有点冗长。

对我来说,使用Objc语言制作通用模块时遇到了问题。 一旦完成,就需要一堆样板代码才能使用它。 当时,我决定制作自己的DSL(特定于域的语言)来解决我的代码问题,结果得到了结果! 这是东西!

这是我面临的情况

我的任务是制作一些“ 超级易用的团队成员 ”模块,该模块可测量用户首次交互的时间。 例如,当用户进入某个页面时,他们会执行某些操作,例如触摸加载显示,但是一旦该页面花费的时间太长,用户期望的操作仅仅是离开该页面,他们就不够耐心!

那么,名为TTI的交互模块的时间是几点? 它确定哪个组件需要花费多少时间来加载。

我知道Facebook如何请求API,但这是一个简单的示例。 为了显示下面的供稿页面,让我们假设它需要一个API请求来获取全部数据,并需要一个请求来从CDN服务器检索图像数据。

在这种情况下,TTI模块会测量这两个网络请求,并计算获取每个信息所需的时间。
好,那就开始吧!

通用Objective-C风格

我采用的模块设计是在加载页面之前声明所有API调用。 使用上面的示例,

“哦,此页面将显示一个供稿。 为此,它需要一个api请求和一个image请求。 然后测量这两件事!”

可以将其更改为类似的代码;

  //一些viewController 
-(void)loadView {
[TTICenter targetPage:@“ newsFeedPage”];
[TTICenter measureRequest:API_REQUEST名为:@“ feedRequest”];
[TTICenter measureRequest:IMAGE_REQUEST名为:@“ attachedImage”];
[TTICenter启动];
}

使用它似乎有些复杂,因为它必须遵循从“ targetPage”到“ start”的某些“方法调用过程”,这并不方便。

因此,我将其放在如下所示的一个方法调用中;

  //一些viewController 
-(void)loadView {
[[TTICenter targetPage:@“ newsFeedPage”
measureRequests:@ [API_REQUEST,IMAGE_REQUEST]
命名:@ [@“ feedRequest,@” attachedImage“]]
开始];
}

现在,新方法看起来更加清晰易懂,并且不再需要遵循所有方法调用过程! 任务完成!

当我快要结束工作时,老板来找我说:“您考虑过A / B测试用例吗? 我希望得到不同案件双方的结果。”等等,什么? 我可以理解您的观点,这是完全可以理解的,但是,为什么您在第一步中没有对我说?

根据我新添加的规范,上面的示例提要视图可以显示为不同类型,具体取决于接收到的数据。 如果接收到的数据表明“我是一个有三张图像的视图,没有文本!”,则结果视图看起来与第一个视图有所不同。

好了,我可以根据不同的视图类型更改TTI方法;

  //一些viewController 
-(void)loadView {
[TTICenter targetPage:@“ newsFeedPage”
viewType:@“ textAndImage”
measureRequests:@ [API_REQUEST,IMAGE_REQUEST]
命名:@ [@“ feedRequest,@” attachedImage“]];
  [TTICenter targetPage:@“ newsFeedPage” 
viewType:@“ onlyImages”
measureRequests:@ [IMAGE_REQUEST,IMAGE_REQUEST,IMAGE_REQUEST]
命名:@ [@“ image1,@” image2“,@” image3“]];
[TTICenter启动];
}
  //在获取有关视图类型的数据之后 
{
...
[TTICenter selectedViewType:@“ onlyImages”];
...
}

由于模块不知道将选择哪种视图类型,因此,所有可能的视图类型都在第一次声明时声明,并且在从服务器接收信息后将选择一种视图类型。

这是一个大问题。 我违反了规则。 “ 易于使用 ”。 如果Feed具有4种不同的视图类型怎么办? 我的团队成员是否必须在每页中写这些样板代码? 没门!

使用DSL:声明我自己的语言

该方法从使用案例开始。 如果我是打算使用该模块的开发人员,那么如何设计使其易于应用?

我从名为Masonry的github模块中获得了很多启发,该模块支持以编程方式应用视图布局约束的简便方法。 这是参考链接;
https://github.com/SnapKit/石工

因此,我的代码使用自己的自定义语言进行了更改;

  //一些viewController 
-(void)loadView {
[[[TTICenterclarifyWith:^(TTIConstraint * make){
make.targetPage(@“ newsFeedPage”);
make.viewType(@“ textAndImage”)。api(@“ feed”)。image(@“ image”);
make.viewType(@“ onlyImages”)。images(@ [image1,image2,image3]);
]开始];
}

现在,不必要的请求类型已更改为“ api”和“ image”,或多个图像的“ images”。 而且,每个相关信息都通过点号(。)表示法链接起来,这点非常容易理解!

怎么运行的

第一个问题是,如何获取圆括号内的值,如“ make.targetPage(“ value”)”?

使用Swift非常简单,您只需为TTIConstraint创建方法即可;

  TTIConstraint类{ 
public final func targetPage(_ page:String){
//.. 做点什么
  //返回自身进行链接 
返回自我
}
}

但是一旦您尝试使用Objective-C来应用它,那就大不一样了。 Objc的方法样式与Swift的方法样式不同,无法使用dot(。)表示法访问方法。 如果您像Swift一样编写代码,

  -(TTIConstraint *)targetPage(NSString * page){ 
//.. 做点什么

//返回自我进行链接
返回自我
}

用例将是;

  ... 
//需要“ make.targetPage()”,但实际上不是
[make targetPage:@“ newsFeedPage”];
...

若要在Objc中使用dot(。)表示法,请返回一个语句块而不是一个自类。

  -(TTIConstraint *(^)(NSString *))targetPage { 
返回^ id(NSString * value){
//.. 做点什么
  //返回自身进行链接 
返回自我
};
}

在此代码中,当您访问make.targetPage时,它将返回block语句。 在该块中,您可以指定运行该块的某些值,在这种情况下为“ NSString * value”,处理该块并返回self。

在“执行某些操作”区域中,您可以使用任何数据结构保留该值。 此类将由链接方法调用使用,以便您可以添加与第一个声明的TTIConstraint类相关的更多信息。

稍后我将添加示例代码,但有些人想首先了解代码结构,您可以引用我之前提到的github代码,Objective-C的Masonry代码,Swift的SnapKit代码。

最终思想

特定领域的语言可能会产生更多的复杂性; 它将学习成本提高到其他开发人员。 但是,一旦以明确定义的语法实现了它并且不经常对其进行更改,我坚信它会有助于使用Objective-C语法进行改进。