iOS利用block實現鏈式編程方法(Objective-C鏈式編程)


objc利用block實現鏈式編程方法

  因為不好讀。block和其他語言的匿名函數一樣,很多程序員剛開始很難主動去用他。

  本文描述block作為屬性的實際使用,看懂block,並講解如何利用block實現鏈式編程方法。

   【更新】LinkBlock支持多對象鏈式編程
    寫法簡單粗暴提升效率
    //【New】支持多對象鏈式編程
    //1.如果使用多個對象的鏈式編程,需要取值的時候請調用ends()獲取多個結果
    //2.如果調用end()只會取得第一個對象的鏈條返回值
    //3.使用endsAt()可以獲取指定index對象的鏈條返回值
    //4.如果鏈條結尾返回的是“值類型”,那么該值是第一個對象的鏈條返回值
    //5.使用項目中的“方法”對多對象的鏈式編程不起作用
    //寫法一
    UIView* viewA = UIViewNew.viewSetFrame(45,100,20,20);
    UIView* viewB = UIViewNew.viewSetFrame(120,100,20,20);
    linkObjs(viewA, viewB).viewAddToView(self.view).viewBGColor([UIColor lightGrayColor]);
    //寫法二
    NSMutableArray* arrA = [NSMutableArray arrayWithObjects:@"A", nil];
    NSMutableArray* arrB = [NSMutableArray arrayWithObjects:@"B", nil];
    NSMutableArray* arrC = [NSMutableArray arrayWithObjects:@"C", nil];
    NSArray* linkResults = @[arrA,arrB,arrC].makeLinkObjs.m_arrAddObj(@"E").ends();
    //寫法三
    linkResults = arrA.linkAnd(arrB).linkAnd(arrC).m_arrAddObj(@"D").ends();
    //寫法四,簡單粗暴的重復執行之后鏈條100次,這種情況不需要for循環了
    linkResults = arrA.linkLoop(100).m_arrAddObj(@"F").ends();

  

  【一】遭遇

  到今天iOS開發中最常用的語言還是objc,市場就像泰坦尼克號,人雖然在上樓,但是船在下沉,所以人還是在下沉。雖然swift出現的迅猛,但是大部分開發者面對的還是objc。什么時候swift替代objc,今天的objc開發人員也不用太着急,說不定船都沉了(蘋果保佑)。

  objc最令人頭疼的是他看起來像部落語言一樣的表達。比如下面這行新手代碼,但是就像求偶訊號一樣晦澀:

 [[NSMutableDictionary alloc] initWithContentsOfURL:[NSURL URLWithString:@"a.txt"]][@"persons"][12];

  但它並沒有告訴別人任何有意義的事情,分解一下語法就像下面一樣:

NSMutableDictionary* dict = [[NSMutableDictionary alloc] initWithContentsOfURL:[NSURL URLWithString:@"a.txt"]];
NSArray* arr = dict[@"persons"];
arr[12];

  語言發展到今天,代碼的普世價值不是效率,而是開發效率。這就是為什么java能夠成為王者的原因。因為java寫起來快,好讀

  【二】探索

  objc比起java又慢又不好讀。那么如何讓objc看起來像java一樣呢。縱觀objc全部語法,只有block能夠辦到。我們來看block的定義

returnType  (^name)  (val1, val2, ...);
 返回類型     變量名        參數列表

//我們可以從表面認為他是一根指向函數的指針,這個函數的樣子如下
(returnType)name(val1, val2, ...)
{
  //......
}

  block具有一個函數的外觀,又被當作一個變量。那么block就具備兩個功能,第一:可以作為類的屬性被'點'出來。第二:可以當作函數直接調用。下面逐個解釋,第一個類的屬性可以點出來,比如person.name;這很好理解,你一定見過,str.length;對吧。第二個呢,block作為一個變量,但是又可以把它當作指向函數的指針一樣調用。

NSString*(^myBlock)() = ^(){ return @"菊樂"; };//定義一個返回值為NSString類型,無參的,並且名字叫做myBlock的block

myBlock();//這一行就是調用

NSString result = myBlock();//這是取出block執行后拿到的返回值,也就是@"菊樂"

  這里再解釋一次block的定義:上面的myBlock可以認為是指向一個定義如下的函數的指針,這個函數是

(NSString*) myBlock()
{
   //...      
}

//指向函數的指針
NSString* (*p) ();

//可以認為指針p就是myBlock(其實block的真實身份更加復雜)

  【三】推理

  我們來幻想一下objc寫起來是這樣的

//設置視圖位置和大小,設置視圖背景色
view.setFrame(0,0,50,50).setBackgroundColor( @"#0c0c0c".toColor() );
//移除空格並在控制台打印字詩句
@" 白 日 依 山 盡 , 黃 河 入 海 流 ".removeStr(@" ").nslog();

  而實際上要寫5行的代碼,一行3秒,3行15秒。就這樣錯過了一次搖一搖的機會,人生在緩慢的艱難,而我們卻還一無所知。

view.frame = CGRectMake(0, 0, 50, 50);
view.backgroundColor= [UIColor colorWithRed:0.5 green:0.5 blue:0.5 alpha:1.0];
    
NSString* str= @" 白 日 依 山 盡 , 黃 河 入 海 流 ";
str= [str stringByReplacingCharactersInRange:@" " withString:@""];
NSLog(str);

  現在我們來倒着分析如何使用block來實現鏈式編程,再來觀察這句話:

view.setFrame(0,0,50,50).setBackgroundColor( @"#0c0c0c".toColor() );

  其中的setFrame和setBackgroundColor他們是什么?大家一定知道是屬性。可是屬性是不帶括號的,setFrame()他是屬性嗎?那他又是什么呢?setFrame()是屬性,並且是block類型的,因為block可以加括號調用。就像這樣調用block();那么還有一個問題很關鍵:view.setFrame().setBackgroundColor();怎么可以連續點出來,這剛開始很不好理解。這並不難,我們往前看那句話:

NSString result = myBlock();

  細心的去發現,block是可以有返回值的,那么我們就依靠這個返回值就可以點出一堆屬性,一直點下去,這樣我們的鏈式編程的思路就通了。

  【四】實踐

  我們現在就來實現view.setFrame().setBackgroundColor();

  首先准備一個分類。頭文件定義如下:

//  UIView+Extension.h

#import <UIKit/UIKit.h>

@interface UIView(Extesion)
@property (nonatomic,copy) UIView* (^setFrame)(CGFloat x, CGFloat y, CGFloat w, CGFloat h);
@property (nonatomic,copy) UIView* (^setBackgroundColor)(UIColor* color);
@end

  我們在UIView類上擴展了兩個額外屬性setFrame和setBackgroundColor,這意味着只要是繼承自UIView的對象就可以點出來這兩個屬性。

  現在我們需要理解一下兩個的區別:

view.setFrame;//這是獲取屬性,它返回一個block
view.setFrame();//這是獲取屬性,它返回一個block,最后我們使用括號調用了這個block,這個block的返回值是UIView類型的對象,那么他就可以繼續點出下一個屬性了

  view.setFrame()這句話的末尾因為加上了括號所以執行了block,而他的返回類型是一個UIView對象,所以可以調用setBackgroundColor();

  這里解釋一下為什么用copy,block這種類型本來是值類型的,他本來是在棧上的,在ARC環境下如果被任何強指針過了一次,編譯器就會把他進行一次copy,放到堆內存中。block的retain行為默認是用copy的行為實現的,因為block變量默認是聲明為棧變量的,為了能夠在block的聲明域外使用應該使用copy。

   接下來實現.m文件內的代碼:

- (UIView *(^)(CGFloat, CGFloat, CGFloat, CGFloat))setFrame
{
    return ?;
}

  不難分析出,這個屬性的類型是block,具體是UIView* (^)(CGFloat , CGFloat , CGFloat , CGFloat )類型。那么‘?’就是這樣一個類型的對象。可是這個對象從哪里獲得呢,如果是個NSString我還存儲的有,而這個對象只能無從獲取。這並不重要,因為我們並不要要block對象本身,我們需要的是:block對象他能夠執行一些功能就夠了。所以屬性內部返回一個臨時block對象,這個block內部呢去執行一些功能,最重要的是block內部一定要返回UIView類型的對象。編譯器會對block的類型進行苛刻的檢測。

- (UIView *(^)(CGFloat, CGFloat, CGFloat, CGFloat))setFrame
{
    return ^(CGFloat x, CGFloat y, CGFloat w, CGFloat h){//返回臨時變量的block
        self.frame = CGRectMake(x, y, w, h);//block執行一些功能
        return self;//block執行完畢的返回值
    };
}

  下面是.m文件的實現

//  UIView+Extension.m

#import "UIView+Extension.h"

@implementation UIView(Extesion)
- (UIView *(^)(CGFloat, CGFloat, CGFloat, CGFloat))setFrame
{
    return ^(CGFloat x, CGFloat y, CGFloat w, CGFloat h){
        self.frame = CGRectMake(x, y, w, h);
        return self;
    };
}
- (void)setSetFrame:(UIView *(^)(CGFloat, CGFloat, CGFloat, CGFloat))setFrame{};//該屬性不需要從外部設置,不想報警告就寫空方法

- (UIView *(^)(UIColor *))setBackgroundColor
{
    return ^(UIColor* color){
        self.backgroundColor= color;
        return self;
    };
}
- (void)setSetBackgroundColor:(UIView *(^)(UIColor *))setBackgroundColor{};
@end

  【五】崩潰

  objc中向nil對象發送任何消息都不會崩潰,但是發送他不能處理的消息類型就會崩潰,這也是最經常遇到了情況。還有這樣也會崩潰:nil.length; view.length;調用不存在的屬性也會崩潰。那么這是我們要處理的一個重要的情況。

  試想,一行代碼中間突然有一個處理返回了nil給下一個環節,一調用就崩潰,關鍵是斷點並不能明確告訴你在哪一行。我們只能從控制台中獲得調試信息。這也是鏈式編程的弊端之一。

  為了處理這種情況,我寫了一個鏈式編程框架LinkBlock來統一處理,針對幾乎所有常用功能進行了鏈式的封裝,並沒有什么門檻,希望大家幫我點一顆星星,支持天朝做良好的程序員。

  [希望大家幫我點一顆星星]

  【六】效率

  關於效率,肯定比原生低那么一丁點的,幾乎可以忽略的,只是多了一次屬性調用,一次一臨時block的創建,一次block的執行。博主的觀點是為了提高開發效率,而降低一些運行效率。有時候也是值得的。最后蘋果也是大力提倡我們使用block的。祝愉快。

 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM