UIResponder響應鏈 關於響應者鏈

1、概述
  UIView與UIViewController的共同父類:UIResponder,對於點擊touches一系列方法,UIView與UIViewController會作出一系列反應,下面從「如何找到點擊的子view」和「如何根據響應鏈響應」兩方面來認識UIResponder。
 
2、 如何找到點擊的子view
  當用戶點擊某一個視圖或者按鈕的時候會首先響應application中UIWindow一層一層的向下查找,直到找到用戶指定的view爲止。
  好比上圖,點擊ViewE,會首先響應application中UIWindow一層一層的向下查找。查到ViewA,發現點擊位置在ViewA內,接下來發現點擊位置在ViewB內,接下來發現點擊位置在ViewE內,就找到了ViewE。主要經過下面兩個方法來找到的:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;   // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;   // default returns YES if point is in bounds

UIWindow會經過調用hitTest:withEvent:方法(這個方法會對UIWindow的全部子view調用pointInside:withEvent:方法,其中返回的YES的視圖爲ViewA,得知用戶點擊的範圍在ViewA中),相似地,在ViewA中調用hitTest:withEvent:方法,得知用戶點擊的範圍在ViewB中,依此類推,最終找到點擊的視圖爲ViewE。html

其中,hitTest:withEvent:方法大體的實現以下:app

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
     for (UIView *view in self.subviews) {
          if([view pointInside:point withEvent:event]){
              UIView *hitTestView = [view hitTest:point withEvent:event];
             if(nil == hitTestView){
                  return view;
              }
          }
      }
     return nil;
 }

經過以上遞歸的形式就能找到用戶點擊的是哪一個view,其中還要注意的是當前的view是否開啓了userIntercationEnabled屬性,若是這個屬性未開啓,以上遞歸也會在未開啓userIntercationEnabled屬性的view層終止。ide

 

3、如何根據響應鏈響應post

  既然找到了用戶點擊的view,那麼當前就應該響應用戶的點擊事件了,UIView與UIViewController的共同父類是UIResponder,他們均可以複寫下列4個方法:url

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; 
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event;

  這個響應點擊事件的過程是上面的逆序操做,這就是用到了UIResponder的nextResponder方法了。好比上面的圖點擊ViewE,這時候ViewE先響應,接下來是nextResponder即ViewB,接下來是ViewB的nextResponder即ViewA。spa

  關於nextResponder有以下幾條規則:.net

1. 當一個view調用其nextResponder會返回其superView;
2. 若是當前的view爲UIViewController的view被添加到其餘view上,那麼調用nextResponder會返回當前的UIViewController,而這個UIViewController的nextResponder爲view的superView;
3. 若是當前的UIViewController的view沒有添加到任何其餘view上,當前的UIViewController的nextResponder爲nil,無論它是keyWinodw或UINavigationController的rootViewController,都是如此;
4. 若是當前application的keyWindow的rootViewController爲UINavigationController(或UITabViewController),那麼經過調用UINavigationController(或UITabViewController)的nextResponder獲得keyWinodw;
5. keyWinodw的nextResponder爲UIApplication,UIApplication的nextResponder爲AppDelegate,AppDelegate的nextResponder爲nil。

用圖來表示,以下所示:code

 

4、遇到的問題htm

  在開發過程當中,咱們有可能遇到UIScrollView 或 UIImageView 截獲touch事件,致使touchesBegan: withEvent:/touchesMoved: withEvent:/touchesEnded: withEvent: 等方法不執行。好比下面這種狀況,scrollView的superView是view,view對應的viewController中的touchesBegan: withEvent:/touchesMoved: withEvent:/touchesEnded: withEvent: 等方法就不執行。blog

若是想讓viewController中的方法執行的話,你可能會提出下面的解決辦法:
@implementation UIScrollView (Touch)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    if([self isMemberOfClass:[UIScrollView class]]) {
        [[self nextResponder] touchesBegan:touches withEvent:event];
    }
}
@end

這樣UIScrollView確實會用nextResponder把響應傳遞到view,接下來傳遞到viewController中。可是,若是沒有使用if([self isMemberOfClass:[UIScrollView class]]) 進行過濾判斷,那麼,有可能會致使一個使用系統手寫輸入法時帶來的crash問題。即手寫的鍵盤的子view是UIKBCandidateCollectionView,調用了[[self nextResponder] touchesBegan:touches withEvent:event];後會形成系統的crash問題:

-[UIKBBlurredKeyView candidateList]: unrecognized selector sent to instance 0x104f6c6b0

這個crash的復現見《UIKBBlurredKeyView candidateList:unrecognized...BUG修復》,它的解決辦法也隨處可見,好比《-[UIKBBlurredKeyView candidateList]: unrecognized selector sent to instance 0x5a89960

此crash的技術層面詳細緣由:

手寫的鍵盤的子view是UIKBCandidateCollectionView(UIColloectionView的子類)的實例,它的nextResponder是UIKBHandwritingCandidateView類型的實例,執行UIKBHandwritingCandidateView的touchesBegan:withEvent:方法後,會使得整個candidate view呈選中狀態,而蘋果對手寫鍵盤的選擇candidate字符時的原生處理方法是會避免candidate view呈選中狀態的。整個candidate view呈選中狀態後後再點擊鍵盤的任意地方,本應調用UIKBCandidateView實例的方法candidateList,結果調用了UIKBBlurredKeyView的candidateList方法,致使方法找不到,致使"-[UIKBBlurredKeyView candidateList]: unrecognized selector sent to instance "crash。

crash總結:

經過對這個crash的詳細分析,雖然上面的使用isMemberOfClass判斷後使用nextResponder對事件響應鏈進行傳遞沒有問題,但因爲nextResponder依然具備不可控性,仍是不建議用category複寫系統的方法,這一點之後必定注意。

謹慎使用Category,特別是覆蓋系統原始方法的category的實現。

 (PS:本文參考文章《關於響應者鏈》)

相關文章
相關標籤/搜索