首先回顧一下代理模式,它的基本說明如下圖:
控制器先成爲子控件的代理(delegate)並實現相應的代理方法,那麼子控件在運作的過程中,遇到某些需要控制器進行配合的場景時,就可以通過delegate屬性調用對應場景的代理方法,實現讓控制器進行對應操作的效果。
塊回調的基本模式如下圖:
塊回調方法的模型是這樣的:在一個方法B內嵌套另一個方法C,當其他地方的方法A使用B這個方法的時候,B方法能夠通過C來將一些信息傳遞迴給A方法,A自己就可以拿到傳回來的這些信息進行操作(C的調用在B內,具體實現在A內)。
下面以一個自定義的segmentControl爲例來演示如何使用塊回調來實現代理的效果,要求點擊這個segmentControl上的按鈕後,頁面會有相應反應。它的效果如下圖:
具體代碼可在以下鏈接下載:
http://download.csdn.net/download/shayneyeorg/8912483
首先看看使用代理模式的實現方法:
(1)、首先定義segmentControl,這個自定義控件是根據初始化方法傳進來的frame和按鈕標題數組把整個segmentControl等分爲對應個數的UIButton,這個最主要的初始化入口方法如下:
- (instancetype)initWithTitles:(NSArray *)titles frame:(CGRect)frame delegate:(id<SYSegmentControlDelegate>) theDelegate { if (self = [super initWithFrame:frame]) { ...//把傳進來的參數賦給自身屬性 self.delegate = theDelegate;//把傳進來的控制器設爲代理 [self configView];//初始化segmentControl的各部分佈局 return self; } return nil; }
(2)、然後在segmentControl裏的UIButton的點擊監聽方法如下:
-(void)buttonClick:(UIButton *)button{ self.selectedIndex = button.tag; //記錄下點擊的是哪個按鈕 [self refreshButton]; //根據記錄的內容刷新按鈕的顯示 //然後使用delegate調用代理方法,讓控制器執行對應代碼 if ([self.delegate respondsToSelector:@selector(segmentControl:didSelectedIndex:)]) { [self.delegate segmentControl:self didSelectedIndex:self.selectedIndex]; } }
(3)、在控制器中,viewDidLoad方法首先調用了usingDelegate方法來初始化segmentControl,usingDelegate方法的代碼如下:
- (void)usingDelegate { NSArray *items = ... CGRect segmentControlFrame = ... SYSegmentControl *segmentControl = [[SYSegmentControl alloc]initWithTitles:items frame:segmentControlFrame delegate:self]; [self.view addSubview:segmentControl]; //refreshView方法會根據self.selectedIndex的內容決定顯示什麼內容 [self refreshView]; }
同時這種模式還需要代理方法的配合,控制器需要實現對應的代理方法,如下:
- (void)segmentControl:(SYSegmentControl *)segmentControl didSelectedIndex:(NSInteger)index { self.selectedIndex = index; [self refreshView]; }
在整個過程中,當segmentControl中的某個按鈕被點中之後,按鈕的點擊監聽方法會被調用,在這個方法中使用delegate(也就是控制器)去調用對應的代理方法,在控制器實現的代理方法中,根據segmentControl傳過來的參數去刷新頁面,顯示對應內容。
然後看看使用塊回調模式的實現方法:
(1)、塊回調模式中,segmentControl的初始化入口方法和代理模式的有所區別,代碼如下:
- (instancetype)initWithTitles:(NSArray *)titles frame:(CGRect)frame callback:(callbackBlock)block{ if (self = [super initWithFrame:frame]) { ...//把傳進來的參數賦給自身屬性 self.block = block; //把傳進來的塊賦給自身屬性 [self configView]; return self; } return nil; }
(2)、segmentControl的按鈕點擊方法修改如下:
//兩種模式共用一個點擊觸發方法,在方法內判斷模式類型 -(void)buttonClick:(UIButton *)button{ ... if (...) { //省略代理模式的代碼 ... }else if (self.block) { //在此處進行塊回調 self.block(self.selectedIndex); } }
(3)、在控制器中,viewDidLoad方法首先調用了usingBlockCallback方法來初始化segmentControl,代碼如下:
- (void)usingBlockCallback { NSArray *items = ... CGRect segmentControlFrame = ... __weak ViewController *weakself = self; SYSegmentControl *segmentControl = [[SYSegmentControl alloc]initWithTitles:items frame:segmentControlFrame callback:^(NSInteger index) { //塊回調會回調到下面這兩句代碼 weakself.selectedIndex = index; [weakself refreshView]; }]; [self.view addSubview:segmentControl]; [self refreshView]; }
在這個過程中,控制器在初始化segmentControl的時候就將需要回調的代碼也定義好了,segmentControl在需要控制器配合的時候,可以直接調用塊實現回調控制器裏的塊代碼的效果,控制器只需在塊代碼裏定義好對應操作即可。
對比兩種方法可以發現,使用塊回調回比使用代理更加輕便,因爲不需要去聲明協議以及代理方法,也不用去實現代理方法,只需把要回調的內容定義在塊參數裏即可。
但是在使用場景比較複雜的情況下,塊代碼回調這種方式就不如代理模式了。比如說要實現在segmentControl剛開始點擊、剛放開點擊或者在某個特定按鈕狀態下點擊另一個按鈕等這些場景中讓控制器做相應反應,那麼塊回調方式實現起來就不如代理模式了,需要定義多個塊參數以對應多種回調情況,會讓segmentControl的初始化方法顯得臃腫。這時候使用代理模式可以比較清晰區分各個場景的邏輯。