objc利用block實現鏈式編程方法
因為不好讀。block和其他語言的匿名函數一樣,很多程序員剛開始很難主動去用他。
本文描述block作為屬性的實際使用,看懂block,并講解如何利用block實現鏈式編程方法。
【一】遭遇
到今天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的blockmyBlock();//這一行就是調用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;//這是獲取屬性,它返回一個blockview.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的。祝愉快。
新聞熱點
疑難解答