將鏈式調(diào)用的DSL進行到底

一、前言

  • 通過本文,你可以了解到 什么是DSL,怎么實現(xiàn)鏈式DSL, 如何去封裝優(yōu)化,以及 輕松使用 UIBezierPath

  • 當然,本文所采用的例子是對 UIBezierPath 的封裝,一句代碼就可以畫貝塞爾曲線,全程沒有任何 [] ,你會發(fā)現(xiàn),Objective-C 原來也可以這樣玩,先給效果吧!

恩,我姓孔
UIBezierPath.fl_path.maker.moveTo(90,200).addLineTo(200,200).addLineTo(130,300).addLineTo(130,450).addLineTo(90,380).addLineTo(250,200).addLineTo(250,450).addLineTo(300,450).addLineTo(300,380).lineWidth(7).lineCapStyle(kCGLineCapRound).lineJoinStyle(kCGLineJoinRound).stroke();

二、什么是DSL(Domain Specific Language)

DSL是一種基于特定領(lǐng)域的語言,它使工作更貼近于客戶的理解,而不是實現(xiàn)本身,這樣有利于開發(fā)過程中,所有參與人員使用同一種語言進行交流。

  • 而不管做什么開發(fā),總有一個任務就是希望能夠更加簡潔、更加語義化地去表達自己的邏輯,鏈式調(diào)用是一種常見的處理方式。

  • 實際iOS開發(fā)中,相信大家應該都用過,比如說常用來做 界面約束布局Masonry ,它的調(diào)用是這樣的,對比系統(tǒng)的 NSLayoutConstraint 約束,確實使用更加方便和易懂,但我覺得還是不夠徹底,因為還是有 [],我能不能把這個也去掉,這個后文會分析。

UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
    make.left.equalTo(superview.mas_left).with.offset(padding.left);
    make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
    make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}]

三、如何實現(xiàn)鏈式DSL

讀了 Masonry 源碼,你應該能發(fā)現(xiàn),有 兩種方式 能實現(xiàn)鏈式 DSL,同時適用了兩種場合(需不需要外部傳參),其實根本來說就一種方式,就是通過重寫屬性的getter方法,只不過屬性返回值不一樣,對應了不同的場合而已。

  • ** 1、通過屬性的getter方法,返回對象本身,此時不需要外界傳參 **

  • ** 2、通過屬性的getter方法,返回block回調(diào),此時可以通過block傳參 **

個人看法,鏈式語法的結(jié)構(gòu)一般可分三層,創(chuàng)建對象 - 中間變量 - 結(jié)束詞 。其中 中間變量 這部分建議使用中間類來處理,一來體現(xiàn)封裝性,對某一部分的功能應該獨立出來處理,避免單個類代碼冗余;二來調(diào)用更加方便清晰,可讀性更強。本文是針對 UIBezierPath 的封裝,查看了 UIBezierPath 的 api ,很容易發(fā)現(xiàn)對應的結(jié)構(gòu)。

  • 1、創(chuàng)建對象方法(就是創(chuàng)建 UIBezierPath 對象的方法)(部分api舉例)
+ (instancetype)bezierPath;

+ (instancetype)bezierPathWithRect:(CGRect)rect;
  • 2、中間變量(就是初始化 UIBezierPath 對象的屬性以及方法,不限于系統(tǒng),可自定義)(部分api舉例)
@property(nonatomic) CGFloat lineWidth;

- (void)moveToPoint:(CGPoint)point;
  • 3、結(jié)束詞(就是結(jié)束當前的鏈式語法,我們都知道,只有當 UIBezierPath 對象調(diào)用 strokefill 才會繪制出來)(部分api舉例)
- (void)fill;

- (void)stroke;

四、鏈式實現(xiàn) UIBezierPath的封裝

為了更加方便大家使用,本次封裝是為 UIBezierPath 添加分類,使用更加方便簡單。

  • 1、中間類創(chuàng)建,本文中間類命名為 FLBezierPathMaker ,繼承自 NSObject。定義需要初始化 UIBezierPath 對象的屬性,將系統(tǒng)的api轉(zhuǎn)換(此時屬性可自定義,不局限于系統(tǒng)的),因為只需要 getter 方法,因此屬性都是 readonly 修飾,如果需要參數(shù)傳入,那么就定義block屬性,如果不需要參數(shù)傳入,普通中間類的對象即可。

    注意:需要鏈式拼接的屬性都需要返回中間類FLBezierPathMaker 對象本身(部分api舉例)

/**

  • @author gitKong
  • 當前繪制BezierPath的顏色,stroke 對應 線,fill 對應填充
    */
    @property (nonatomic,weak,readonly)FLBezierPathMaker (^color)(UIColor color);
    /
  • @author gitKong
  • 起點,可多次設置,對應系統(tǒng)api:@property(nonatomic) CGFloat lineWidth;
    /
    @property (nonatomic,weak,readonly)FLBezierPathMaker (^lineWidth)(CGFloat lineWidth);
    /
  • @author gitKong
  • 起點,可多次設置,對應系統(tǒng)api:- (void)moveToPoint:(CGPoint)point;
    */
    @property (nonatomic,weak,readonly)FLBezierPathMaker *(^moveTo)(CGFloat x,CGFloat y);

- 2、創(chuàng)建對象,參考 [Masonry ](https://github.com/SnapKit/Masonry) 的做法,通過傳入設置中間變量的 `makeOperation` 回調(diào),并實現(xiàn),這個回調(diào)參數(shù)是 `FLBezierPathMaker` 在創(chuàng)建對象的時候傳遞出去。從而實現(xiàn) `bezierPath` 的初始化信息。(部分api舉例)

/**

  • @author gitKong
  • 回調(diào)通過 maker 初始化中間類信息,對比系統(tǒng)方法:+ (instancetype)bezierPath;
    */
  • (instancetype)fl_bezierPath:(void(^)(FLBezierPathMaker maker))makeOperation;
    /
    *
  • @author gitKong
  • 回調(diào)通過 maker 初始化中間類信息,對比系統(tǒng)方法:+ (instancetype)bezierPathWithRect:(CGRect)rect;
    */
  • (instancetype)fl_bezierPathWithRect:(CGRect) rect makeOperation:(void(^)(FLBezierPathMaker *maker))makeOperation;

- 3、結(jié)束詞,通過結(jié)束詞,結(jié)束當前的鏈式語法,并且實現(xiàn)相對于的功能,本文中就是實現(xiàn)繪制貝塞爾曲線,同樣,`stroke` 和 `fill` 的api已經(jīng)在中間類 `FLBezierPathMaker` 中定義。(部分api舉例)

/**

  • @author gitKong
  • 默認繪制顏色為UIColor.blackColor,對應系統(tǒng)方法:- (void)stroke;- (void)fill;
    */
    @property (nonatomic,weak,readonly)UIBezierPath * (^stroke)();
    @property (nonatomic,weak,readonly)UIBezierPath * (^fill)();

- 4、調(diào)用,類似 `Masonry ` 的調(diào)用方式,是不是覺得鏈式語法實現(xiàn)很簡單

[UIBezierPath fl_bezierPath:^(FLBezierPathMaker *maker) {
maker.moveTo(0,20).addLineTo(30,50).stroke();
}];


---

# 五、精簡鏈式DSL代碼

> 上文提過,此時調(diào)用還是需要使用 `[]` 創(chuàng)建對象,能不能像 swift 一樣,可以直接去掉 `[]` ,只需要通過 `類名+點語法` 就能實現(xiàn)對象的創(chuàng)建呢

- 要實現(xiàn)通過 `類名+點語法` 創(chuàng)建對象,必須是一個 `getter` 方法,而且這個 `getter` 方法是類方法。簡單來說,就是需要 **類屬性** 的 `getter` 方法!

- 屬性就是對象嘛,雖然類屬性在 Objective-C 是存在的,因為一切皆對象,但以前從來沒有能顯式開放使用,但這個東西在 Swift 中是有的,例如:`public var characters: String.CharacterView`,是 `String` 的一個類屬性。

- 可喜的是,在 [WWDC 2016 What’s New in LLVM](https://developer.apple.com/videos/play/wwdc2016/405/) 中(文字在 Transcript 中),有提到從 X-code 8開始,LLVM已經(jīng)支持 Objective-C 顯式聲明類屬性了,這是為了與 Swift 中的類屬性互操作而引入的,原文如下:

>Objective-C now supports class properties. This feature started in Swift as type properties, and we've brought it to Objective-C. Interoperation works great. In this example, the class property someString is declared using property syntax, by adding a class flag. Later, this someString property is accessed using dot syntax. Class properties are never synthesized.

>Objective-C現(xiàn)在支持類屬性。 這個特性從Swift開始作為類型屬性,我們把它帶到Objective-C。 互操作就可以很好處理了。 在此示例中,通過添加類標志class,使用屬性語法聲明類屬性someString。 然后,就可以使用點語法訪問此someString屬性。 **注意:類屬性系統(tǒng)不會幫我們實現(xiàn)(setter 和 getter 方法)**。

不會聲明的,可以在查看系統(tǒng)的 `UIColor` 類:你用X-code 8 以上的話,都可以像Swift一樣獲取顏色:`UIColor.redColor` (不過系統(tǒng)沒提示,我的x-code版本: Version 8.2.1 (8C1002))

![截圖來自蘋果官方API](http://upload-images.jianshu.io/upload_images/1085031-11051d866a16b157.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


---

那么現(xiàn)在就很好辦了,創(chuàng)建對象可以直接使用類屬性來實現(xiàn)了返回,而從上文可知,需要提供一個參數(shù)  `makeOperation` 回調(diào),那就意味著,我們需要一個block 的類屬性,這個block類屬性返回值是 `UIBezierPath` 對象,參數(shù)只有一個,就是 `makeOperation` 回調(diào)block(`makeOperation` 參數(shù)是`FLBezierPathMaker`對象,不需要返回值),為了方便大家理解,可看下面代碼(h 和 m文件):

/**

  • @author gitKong

  • 通過添加 class 修飾,就是類屬性

  • fl_bezierPath:參數(shù):makeOperation(block);返回值:UIBezierPath *

  • makeOperation(block):參數(shù):FLBezierPathMaker *;返回值:void

*/
@property (class,nonatomic,weak,readonly)UIBezierPath *(fl_bezierPath)(void(makeOperation)(FLBezierPathMaker *));


UIBezierPath * FLCreatePathMaker(UIBezierPath *path,void (^makeOperation)(FLBezierPathMaker *)){
// 初始化 中間類 對象
FLBezierPathMaker *mk = [[FLBezierPathMaker alloc] init];
mk.path = path;
// 實現(xiàn)傳遞過來 makeOperation,并傳出參數(shù):FLBezierPathMaker 對象
if (makeOperation) {
makeOperation(mk);
}
// 存儲當前的 FLBezierPathMaker 對象
_maker = mk;
return path;
}

  • (UIBezierPath * (^)(void (^)(FLBezierPathMaker )))fl_bezierPath{
    /
    *
    • @author gitKong
    • 類屬性需要調(diào)用者手動實現(xiàn) getter 方法
      */
      return ^(void (^makeOperation)(FLBezierPathMaker *)){
      // 傳遞參數(shù),返回 UIBezierPath 對象
      return FLCreatePathMaker([UIBezierPath bezierPath],makeOperation);
      };
      }

---

那么現(xiàn)在創(chuàng)建對象就不需要通過 `[]` 實現(xiàn)了,勇敢地點出來吧(x-code 不提示)

UIBezierPath.fl_bezierPath(^(FLBezierPathMaker *maker) {
maker.moveTo(0,20).addLineTo(30,50).stroke();
});


對比一下之前寫的 `[]` ,是不是覺得舒服很多了,雖然現(xiàn)在全部都是點語法了,但是整體代碼看上去不是那么好讀,而且還有一個可優(yōu)化的點,就是屬性名稱太長了(你可以理解為方法名),寫過 swift的都知道,swift 的api 都是很精簡,同時因為x-code不提示,因此 `^(FLBezierPathMaker *maker) {}` 都是要自己手寫上去,這就不友好了,因為要手寫的太多了,舉個例子:(選個參數(shù)最多的)

/**

  • @author gitKong
  • 繪制圓弧,對應系統(tǒng)方法:+ (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
    */
    @property (class,nonatomic,weak,readonly)UIBezierPath *(^fl_arcCenter)(CGPoint center,CGFloat radius,CGFloat startAngle,CGFloat endAngle,BOOL clockwise,void(^maker)(FLBezierPathMaker *));

/**

  • @author gitKong
  • 因為x-code 不提示,我最快的方法只能通過去h文件copy過來填寫,參數(shù)太多,而且參數(shù)還帶個block,太麻煩
    */
    UIBezierPath.fl_arcCenter(CGPointMake(30, 30),30,0,60,YES,^(FLBezierPathMaker *maker){
    maker.moveTo(0,20).addLineTo(30,50).stroke();
    });

那么怎么去掉`^(FLBezierPathMaker *maker) {}` 呢?

- 首先我們要清楚,這個東西是干什么的,這個是外界傳入的一個block,這個block提供一個 `FLBezierPathMaker ` 對象可以讓外界設置中間類的屬性,同時又能返回當前的 `UIBezierPath` 實例,那么我們可以分開這兩個功能,通過兩個屬性分別實現(xiàn):**(如果需要傳遞參數(shù),那么就使用block屬性,注意不要帶入`FLBezierPathMaker ` 參數(shù)就行)**
  

/**

  • @author gitKong
  • 對外提供的 中間類對象,設置中間類的對象屬性
    /
    @property (nonatomic,weak,readonly)FLBezierPathMaker maker;
    /
  • @author gitKong
  • 類屬性,創(chuàng)建UIBezierPath實例
    */
    @property (class,nonatomic,weak,readonly)UIBezierPath *fl_path;

static FLBezierPathMaker *_maker = nil;

  • (FLBezierPathMaker *)maker{
    return _maker;
    }
  • (UIBezierPath *)fl_path{
    return FLCreatePath([UIBezierPath bezierPath]);
    }
    UIBezierPath * FLCreatePath(UIBezierPath *path){
    FLBezierPathMaker *mk = [[FLBezierPathMaker alloc] init];
    mk.path = path;
    // 存儲當前的FLBezierPathMaker
    _maker = mk;
    return path;
    }

此時調(diào)用起來就是我所需要的效果了,而且屬性名稱精簡了,需要傳入的參數(shù)也少了,看代碼翻譯成自然語言就是:**創(chuàng)建一個path,讓移動起點到(0,20),添加線條到點(30,50),繪制**。

UIBezierPath.fl_path.maker.moveTo(0,20).addLineTo(30,50).stroke();


對比系統(tǒng)方式調(diào)用:

UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, 20)];
[path addLineToPoint:CGPointMake(30, 50)];
[path stroke];


---

# 六、總結(jié)

- 1、以上是封裝的思路以及講解鏈式語法是如何實現(xiàn)的,不單是Swift,OC也可以寫得很優(yōu)雅。

- 2、封裝了 `UIBezierPath` 類,調(diào)用起來很方便,特別是如果繪制線條比較多的時候,可讀性更強。

- 3、也許你會有個疑惑,為什么 `stroke` 和 `fill` 設計成一個block,而不是單純一個普通屬性,因為根本不需要傳參!確實可以這樣實現(xiàn),而且能正常使用,但會報警告??(如下圖),意思就是:**不要用.調(diào)用,用[]括號調(diào)用** 如果這樣不就是回到原點么,因此使用block來實現(xiàn),就能避免這種警告。

![報警告是不是很不爽](http://upload-images.jianshu.io/upload_images/1085031-2610015efc6b880c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

- 4、本文封裝的 `stroke` 和 `fill` block,能返回當前的 `UIBezierPath` 對象,而系統(tǒng)的 `stroke` 和 `fill` 是返回 `void`,因為考慮到繪制完畢后可能還需要操作path。

- 5、部分方法需要設置角度angle的值,系統(tǒng)默認是以弧度表示,框架內(nèi)部處理了,比如你使用 `addArcWith`,設置 `startAngle` 值為 0,那么角度就是 0 度。

- 6、如果你有什么疑惑或建議,歡迎留言!同時歡迎大家關(guān)注我,喜歡點個like,會不定時更新技術(shù)文章。

- 7、框架支持cocoaPod,輸入:`pod search FLBezierPath` 查詢最新版本 ,對應 [Github 地址](https://github.com/gitkong/FLBezierPath)  如果覺得可以,點個star喔

![FLBezierPath](http://upload-images.jianshu.io/upload_images/1085031-7dca2528341b3537.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/620)


文章已經(jīng)同步到我的 [個人博客](https://gitkong.github.io)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容