Objective-C实现链式编程语法(DSL)

作者 : 开心源码 本文共7236个字,预计阅读时间需要19分钟 发布时间: 2022-05-12 共96人阅读

您越焦急开始写代码,代码就会花费越长的时间。 – Carlson, University of Wisconsin

前言

熟习Objective-C这一门编程语言的人都知道,Objective-C中方法的调使用都是通过中括号[]实现的。比方[self.view addSubview:xxxView];假如想要在一个对象上连续调使用多个方法,就要用多组中括号嵌套(当然要保证每个方法都能把该对象作为返回值return)。比方[[[UILabel alloc] init] setText:@"xxx"];。这对于有其余编程语言经验的开发者而言,Objective-C无异于就是众多语言中的一朵奇葩。由于其余多数的高级语言方法调使用都是以点语法.的形式实现的。好在Objective-C在iOS4.0之后推出了block这个语法(相当于其余语言中的匿名函数)。我们可以利使用block的来实现Objective-C方法的链式调使用。像这种使用于特定领域的表达方式,我们叫做 DSL (Domain Specific Language),本文就详情一下如何让Objective-C实现链式调使用,其最终调使用方式如下:

DSLObject *obj = DSLObject.new.name(@"ws").age(27).address(@"beijing");

很显著,相比较传统的Objective-C的方法调使用方式,用点语法进行方法调使用更加简洁连贯、一气呵成。
不难看出,这种点语法连续调使用的方式,需要保证每次调使用都能返回对象本身,这样链式调使用才得以继续,并且在必要的时候还可以传入参数,比方上例中的“ws”、“27”、“beijing”。
而至于为什么用block来实现DSL链式调使用语法?正是由于block完全符合构造链式调使用的要求:既可以接收参数,又可以有返回值。
不喜欢读文章的可以直接看代码。

链式调使用的实现

现在要给系统原生的类增扩展链式调使用语法。比方给UIView的frame、backgroundColor添加链式调使用,目前能想到的有以下两种实现方式。

  1. 第一种方式是用category给UIView类扩展少量方法,每个方法的返回值都是一个block,block的参数是要给UIView对象的属性设置的值(比方frame),block的返回值是一个UIView对象。block接收到传入的参数后,会对view对象的响应属性进行赋值,而后把view对象作为返回值返回。开发者想用链式调使用,必需要调使用category中的方法
  2. **第二种方式是为我们要支持链式调使用的系统类(比方UIView类)添加一个中间类(比方叫做DSLViewMaker),DSLViewMaker对象内部持有一个UIView对象,而后DSLViewMaker会公告并实现少量和UIView同名的方法。和方式逐个样,每个方法的返回值也是一个block,block的参数是要给UIView对象的属性设置的值,block的返回值是这个UIView对象**。而后在合适的时候把这个view对象返回给调使用者。

下面针对于两种实现方式分别说明。

category方式实现

/// category 头文件@interface UIView (DSL)- (UIView* (^)(CGRect))DSL_frame;- (UIView* (^)(UIColor *))DSL_backgroundColor;@end
/// category 实现文件#import "UIView+DSL.h"#define weak_Self __weak typeof(self) weakSelf = self#define strong_Self __strong typeof((weakSelf)) strongSelf = (weakSelf)@implementation UIView (DSL)- (UIView *(^)(CGRect))DSL_frame {    weak_Self;    return ^UIView* (CGRect frame) {        strong_Self;        strongSelf.frame = frame;        return strongSelf;    };}- (UIView *(^)(UIColor *))DSL_backgroundColor {    weak_Self;    return ^UIView* (UIColor *backgroundColor) {        strong_Self;        strongSelf.backgroundColor = backgroundColor;        return strongSelf;    };}@end
/// 用户端调使用/// 用户端调使用category指定的带有“DSL_”前缀的方法UIView *view = UIView.new.DSL_frame(CGRectMake(0, 0, 100, 250)).DSL_backgroundColor([UIColor orangeColor]);

那么问题来了,现在要给UIImageView的少量方法和属性添加DSL的链式调使用语法。由于UIImageView继承自UIView,这就代表UIImageView还要拥有UIView的DSL_frame方法和DSL_backgroundColor方法。经过简单的实现,大致如下:

/// UIImageView category的头文件@interface UIImageView (DSL)- (UIImageView* (^)(UIImage *))DSL_image;- (UIImageView* (^)(UIImage *))DSL_HighlightedImage;- (UIImageView* (^)(BOOL))DSL_UserInteractionEnabled;- (UIImageView* (^)(BOOL))DSL_highlighted;- (UIImageView* (^)(NSArray <UIImage *> *))DSL_AnimationImages;- (UIImageView* (^)(NSArray <UIImage *> *))DSL_HighlightedAnimationImages;- (UIImageView* (^)(NSTimeInterval))DSL_AnimationDuration;- (UIImageView* (^)(NSInteger))SDL_AnimationRepeatCount;- (UIImageView* (^)(UIColor *))DSL_TintColor;@end
#import "UIImageView+DSL.h"#define weak_Self __weak typeof(self) weakSelf = self#define strong_Self __strong typeof((weakSelf)) strongSelf = (weakSelf)@implementation UIImageView (DSL)- (UIImageView* (^)(UIImage *))DSL_image {    weak_Self;    return ^UIImageView *(UIImage *image) {        strong_Self;        strongSelf.image = image;        return strongSelf;    };}- (UIImageView* (^)(UIImage *))DSL_HighlightedImage {    weak_Self;    return ^UIImageView *(UIImage *highlightedImage) {        strong_Self;        strongSelf.highlightedImage = highlightedImage;        return self;    };}- (UIImageView* (^)(BOOL))DSL_UserInteractionEnabled {    weak_Self;    return ^UIImageView *(BOOL userInteractionEnabled) {        strong_Self;        strongSelf.userInteractionEnabled = userInteractionEnabled;        return strongSelf;    };}/// 此处省略...,请自行脑补...@end
/// 用户端调使用UIImageView *imageView = UIImageView.new.DSL_frame(CGRectMake(100, 100, 100, 60)).DSL_image([UIImage imageNamed:@"imgxxx"]);

基于以上代码,而后进行编译,编译器会报以下错误:

报错

DSL_image这个东西在UIView中找不到,为什么是UIView呢?明明我们创立的是一个UIImageView。起因很简单,由于我们的DSL_frame是在UIView的category中公告并实现的,更要命的是,UIView(DSL)中公告的DSL_frame这个方法返回的block的返回值是一个UIView对象,UIView对象当然没有DSL_image方法。当DSL_frame返回的block返回了一个UIView类型的对象后,这个imageView就会被当成UIView用,后面所有对UIImageView的方法的调使用都不会成功,UIView(DSL)公告的方法如下:

 - (UIView* (^)(CGRect))DSL_frame;,

针对于这个问题,目前笔者只想到一种处理方法:把在UIView(DSL)中公告的方法拷贝一份到UIImageView(DSL).h中,并修改block的返回值类型为UIImageView。最终的UIImageView(DSL)头文件 如下:

@interface UIImageView (DSL)#pragma mark - UIView/// 这些是在UIView(DSL)中拷贝过来的方法,不同的是,需要修改block的返回值类型为UIImageView,而不是原来的UIView,如下所示:- (UIImageView* (^)(CGRect))DSL_frame;- (UIImageView* (^)(UIColor *))DSL_backgroundColor;#pragma mark - UIImageView- (UIImageView* (^)(UIImage *))DSL_image;- (UIImageView* (^)(UIImage *))DSL_HighlightedImage;- (UIImageView* (^)(BOOL))DSL_UserInteractionEnabled;- (UIImageView* (^)(BOOL))DSL_highlighted;- (UIImageView* (^)(NSArray <UIImage *> *))DSL_AnimationImages;- (UIImageView* (^)(NSArray <UIImage *> *))DSL_HighlightedAnimationImages;- (UIImageView* (^)(NSTimeInterval))DSL_AnimationDuration;- (UIImageView* (^)(NSInteger))SDL_AnimationRepeatCount;- (UIImageView* (^)(UIColor *))DSL_TintColor;@end

而UIImageView(DSL).m实现文件中不需要再实现DSL_frame和DSL_backgroundColor这两个方法,由于已经在UIView(DSL).m中实现过。只要要消除对应的警告就可。

综上,通过category的方式实现链式调使用好处在于每次调使用都会返回对象本身,缺点在于category中的方法不能和系统的方法重名,因而笔者在这里用了一个前缀DSL_来进行区分。而中间类方式实现链式调使用即可以避免前缀的问题。

中间类方式实现

上面已经说过,用category的方式给类扩展链式调使用的方法,我们必需要和原生的方法进行区分(比方添加前缀)。这样的缺点在于开发者开发者链式调使用的时候还必需要时刻谨记调使用指定前缀的方法,用起来不是很友好。
所以,还有另一种方法,我们可以用一个中间类,中间类持有一个UIView对象,给这个中间类添加和UIView同名的方法,通过调使用这个中间类的方法来间接调使用UIView对象的方法。具体实现如下:

/// DSLViewMaker.h文件@interface DSLViewMaker : NSObjectDSLViewMaker *alloc_view(void);/// 少量和UIView同名的方法- (DSLViewMaker *(^)(CGRect))frame;- (DSLViewMaker *(^)(UIColor *))backgroundColor;/// 返回DSLViewMaker配置的对象- (id)view;@end
/// DSLViewMaker.m文件#import "DSLViewMaker.h"#define weak_Self __weak typeof(self) weakSelf = self#define strong_Self __strong typeof((weakSelf)) strongSelf = (weakSelf)@interface DSLViewMaker()@property(nonatomic, strong) UIView *view;@endDSLViewMaker *alloc_view(void) {    return DSLViewMaker.new;}@implementation DSLViewMaker- (instancetype)init {    if (self = [super init]) {        _view = [UIView new];    }    return self;}- (DSLViewMaker *(^)(CGRect))frame {    weak_Self;    return ^DSLViewMaker *(CGRect frame) {        strong_Self;        strongSelf.view.frame = frame;        return strongSelf;    };}- (DSLViewMaker *(^)(UIColor *))backgroundColor {    weak_Self;    return ^DSLViewMaker *(UIColor *backgroundColor) {        strong_Self;        strongSelf.view.backgroundColor = backgroundColor;        return strongSelf;    };}- (id)view {    return _view;}@end
/// 用户端调使用    UIView *view = alloc_view().frame(CGRectMake(0, 20, 100, 100)).backgroundColor([UIColor redColor]).view;    [self.view addSubview:view];

看完上面的代码,你可能会有几个疑惑:

  1. 为什么用户端进行链式调使用是以一个函数开头的?
  2. 为什么最后要用一个.view来返回我们创立的view?

针对于第一个问题,我们是以一个中间类DSLViewMaker来创立了一个view,而后链式调使用DSLViewMaker的对象方法对这个view进行配置。为了不让外部调使用的用户端感知到DSLViewMaker的存在,所有用了一个函数直接返回一个DSLViewMaker对象。

针对于第二个问题,还是由于中间类,由于链式调使用要保证每次都要返回链式调使用的对象(这里是指的maker对象),而用户端无法拿到maker配置好的view,为了让用户端能够获取链式调使用配置好的view对象,所以暴露了一个view方法供外部调使用。

假如你觉得用函数作为链式调使用的开头不够面向对象。那么还可以给UIView添加一个如下分类:

/// category头文件#import <UIKit/UIKit.h>@class DSLViewMaker;@interface UIView (DSLMaker)+ (DSLViewMaker *)make;@end
/// category实现文件#import "UIView+DSLMaker.h"#import "DSLViewMaker.h"@implementation UIView (DSLMaker)+ (DSLViewMaker *)make {    return [DSLViewMaker new];}@end

而后用户端的调使用就变成了这样:

    //    UIView *view = alloc_view().frame(CGRectMake(0, 20, 100, 100)).backgroundColor([UIColor redColor]).view;//    [self.view addSubview:view];UIView *view = UIView.make.frame(CGRectMake(0, 20, 100, 100)).backgroundColor([UIColor redColor]).view;[self.view addSubview:view];

总结

综上,Objective-C语言实现链式语法可以有两种形式,但最终都是用block实现的。用category实现链式语法,需要加前缀。用中间类来实现链式语法,需要有一个特定的方法返回被配置的对象。两种方式各有利弊。
最后附上代码地址。

文/VV木公子(简书作者)
PS:如非特别说明,所有文章均为原创作品,著作权归作者所有,转载请联络作者取得受权,并注明出处。

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Objective-C实现链式编程语法(DSL)

发表回复