Responder一點也不神秘————iOS使用者響應者鏈完全剖析

來源:互聯網
上載者:User

標籤:

http://blog.csdn.net/mobanchengshuang/article/details/11858217?utm_source=tuicool&utm_medium=referral

 

這篇文章想跟大家分享的主旨是iOS捕獲使用者事件的各種情況,以及內部封裝的一些特殊事件。

 

我們先從UIButton談起,UIButton大家使用的太多了,他特殊的地方就在於其內建的普通Default/高亮Highlighted/選擇Selected/可用Enable的幾個狀態(UIControlState)。其次就是SDK內部已經為我們封裝了以下使用者事件:

 

最常用的莫過於Touch Up Inside這個事件了,他代表:  使用者在按鈕地區內按下,並且也在按鈕地區內鬆開。

關鍵點:按下並且鬆開 才能觸發此方法,也就是正確的操作 按下一次,鬆開一次只會觸發一次此事件。與之不同的Touch Drag Inside等方法不需要鬆開這個過程,Up變為了Drag,其實大家都能理解,SDK在封裝的時候原理跟UITouchEvent是一個道理,第一個單詞Touch 代表按下(Began)第二個單詞Up代表鬆開(Ended),Drag代表拖動(Moved)。TouchMoved方法在一次完整的觸摸中會被觸發很多次,所以Touch Drag Inside方法會在使用者手鬆開之前一直被觸發。

這些就是UIButton已封裝的事件,而UIButton繼承自UIControl。UIControl又繼承自UIView。我們平時能用這些已封裝的事件的控制項都是UIControl的子類。那麼父類UIView是沒有內部事件的。

我們常常利用UIView來寫自己的UITouchEvent。例如在一個View/ViewController中直接實現以下3個方法:

 

[cpp] view plain copy

 

print?

  1. -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event  
  2. {  
  3.       
  4. }  
  5. -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event  
  6. {  
  7.       
  8. }  
  9.   
  10. -(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event  
  11. {  
  12.       
  13. }  
  14. -(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event  
  15. {  
  16.       
  17. }  

 

我們用的非常多,但是大家知道這4個方法是誰的執行個體方法嗎?如果你一下就說出是UIView的,那麼為什麼我們在UIViewController中也可以用呢,他們不是繼承關係。

注意這4個執行個體方法來自UIView與UIViewController的共同父類:UIResponder。它是我們今天的主角。

基本上我們所能看到的所有圖形介面都是繼承自UIResponder的,So,它究竟為何方神聖?

UIResponder所謂很多視圖的父類,他掌管著使用者的操作事件分發大權。如果沒有他,我們的電容屏如何將使用者的操作傳遞給我們的視圖令其做出反應呢?

我們先看看iOS中的響應者鏈的概念:

每一個應用有一個響應者鏈,我們的視圖結構是一個N叉樹(一個視圖可以有多個子視圖,一個子視圖同一時刻只有一個父視圖),而每一個繼承UIResponder的對象都可以在這個N叉樹中扮演一個節點。當分葉節點成為最高響應者的時候,從這個分葉節點開始往其父節點開始追朔出一條鏈,那麼對於這一個分葉節點來講,這一條鏈就是當前的響應者鏈。響應者鏈將系統捕獲到的UIEvent與UITouch從分葉節點開始層層向下分發,期間可以選擇停止分發,也可以選擇繼續向下分發。

 

例子:

我用SingleView模板建立了一個新的工程,它的主Window上只有一個UIViewController,其View之上有一個Button。這個項目中所有UIResponder的子類所構成的N叉樹為這樣的結構:

 

那麼他看起來並不像N叉樹,但是不代表者不是一顆N叉樹,當我們項目複雜之後,這個View可不可以有多個UIButton節點?所以他就是一棵樹。

實際上我們要把這棵樹寫完整,應該還要算上UIButton的UILabel和UIImageView,因為他們也是UIReponder的子類。這裡先不考慮了。

我們對UIButton來講,他此時若是分葉節點,那麼這時我們針對他所在的響應鏈來說,他在他之前的響應者就應該是我們controller的view(樹中的分葉節點比父節點永遠更優先被分發事件,但是並不是說他就能在時間上先響應,我們下面講為什麼)。所以我們嘗試在任意地方列印這個Button的nextReponder對象。nextResponder對象是UIReponder類的執行個體方法,它會返回任意對象在樹中的上一個響應者執行個體:

 

[cpp] view plain copy

 

print?

  1. NSLog(@"%@",_testButton.nextResponder);  

 

控制台輸出訊息:

 

 

2013-09-21 03:40:25.989 響應鏈[614:60b] <UIView: 0x16555e10; frame = (0 0; 320 568); autoresize = RM+BM; layer = <CALayer: 0x16555e70>>

 

我們可以根據這個UIView的尺寸來得知,他就是我們唯一的控制器中的那個UIView。

接下來我們再列印下這個UIView的下一個響應者是誰:

 

[cpp] view plain copy

 

print?

  1. NSLog(@"%@",_testButton.nextResponder.nextResponder);  

 

輸出:

 

 

2013-09-21 03:45:03.914響應鏈[621:60b] <RSViewController: 0x15da0e30>

依次看,接著加一個nextResponder:

 

2013-09-21 03:50:49.428 響應鏈[669:60b] (null)

為什麼這裡ViewController沒有父親呢?

注意這句代碼我是寫在ViewDidLoad中,而我們知道這個方法的生命週期比較早,所以我們換個地方寫或者延遲一段時間再列印,兩種方法都可以得到結果(由此可以推理出我們響應者樹的構造過程是在ViewDidLoad周期中來完成的,這個函數會將當前執行個體的構成的響應者子樹合并到我們整個根樹中):

 

2013-09-21 03:53:47.304 響應鏈[681:60b] <UIWindow: 0x14e24200; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x14e242e0>; layer = <UIWindowLayer: 0x14e244a0>>

再繼續往上追朔:

 

[cpp] view plain copy

 

print?

  1. double delayInSeconds = 2.0;  
  2.    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));  
  3.    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){  
  4.         NSLog(@"%@",_testButton.nextResponder.nextResponder.nextResponder.nextResponder);  
  5.    });  
  6.     

 

 

2013-09-21 03:56:22.043 響應鏈[690:60b] <UIApplication: 0x15659c00>

再加一個:

 

2013-09-21 03:56:51.186 響應鏈[696:60b] <RSAppDelegate: 0x16663520>

那麼我們的appDelegate還有沒有父節點?

 

2013-09-21 03:57:22.588 響應鏈[706:60b] (null)

沒有了,注意,一個從分葉節點開始分發的事件,最多也就只能分發到我們的AppDelegate了!

 

這個樹形結構在我們的項目中尤為重要,舉個栗子,如果我們想在一個view中重寫UITouchEvent的4個方法,並且不影響他的父視圖也響應這些事件,就要注意你重寫的方式了,比如我們在ViewController中重寫touchBegan如下:

 

[cpp] view plain copy

 

print?

  1. -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event  
  2. {  
  3.     NSLog(@"ViewController接收到觸摸事件");  
  4. }  

 

在appDelegate的中同樣也寫上這一段:

 

 

[cpp] view plain copy

 

print?

  1. -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event  
  2. {  
  3.     NSLog(@"appDelegate接收到觸摸事件");  
  4. }  

 

 

 

那麼究竟是誰被觸發呢?

 

2013-09-21 04:02:49.405 響應鏈[743:60b] ViewController接收到觸摸事件

這個很好理解,我剛剛也說了,viewController明顯是appDelegate的子節點,他有事件分發的優先權。如果我們想兩個地方都觸發呢?這裡super一下就可以了:

 

[cpp] view plain copy

 

print?

  1. -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event  
  2. {  
  3.     [super touchesBegan:touches withEvent:event];  
  4.     NSLog(@"ViewController接收到觸摸事件");  
  5. }  

輸出:

 

 

2013-09-21 04:07:26.206 響應鏈[749:60b] appDelegate接收到觸摸事件

2013-09-21 04:07:26.208 響應鏈[749:60b] ViewController接收到觸摸事件

注意看時間戳記,appDelegate雖然優先順序別不如ViewController,但是他響應的時間上面足足比ViewController早了0.002秒,我這裡試了幾次,都是相差0.002秒。

那麼我們分析一下這裡的響應者鏈是怎樣工作的:

使用者手指觸摸到了UIView上,由於我們沒有重寫UIView的UITouchEvent,所以他裡面和super執行的一樣的,將該事件繼續分發到UIViewController;

UIViewController的TouchBegan被我們重寫了,如果我們不super,那麼我們在這裡寫響應代碼。事件到這裡就不繼續分發了。可想而知,UIViewController祖先節點:UIWindow,UIApplication,AppDelegate都無權被分發此事件。

如果我們super了TouchBegan,那麼此次觸摸事件由

ViewController分發給UIWindow,

UIWindow繼而分發給UIApplication,

UIApplication再分發給AppDelegate,

於是我們在ViewController和appDelegate的touchBegan方法中都捕獲到了這次事件。

 

到這裡大家應該對這個響應者樹有一個很好的理解了吧?

接下來我們再談談第一響應者,和UIButton上的事件分發。

Responder一點也不神秘————iOS使用者響應者鏈完全剖析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.