iOS 事件傳遞和處理

前言

iPhone擁有很好的用戶交互體驗(yàn),這源于iOS系統(tǒng)對交互事件的高效處理和高優(yōu)響應(yīng)沙热;
App開發(fā)者處理用戶交互非常便捷叉钥,這源于iOS系統(tǒng)和UIKit對用戶操作做了封裝和默認(rèn)處理;
本文圍繞iOS的事件傳遞和處理篙贸,探究其具體過程投队。

正文

什么是事件?

這里講的事件是用戶交互的抽象爵川,像IOHIDEvent和UIEvent都是不同處理階段的封裝敷鸦。

IOHIDEvent是iOS系統(tǒng)對事件的封裝,感興趣可以看源碼IOHIDEvent.hIOHIDEvent.cpp(HID是Human Interface Device的縮寫)寝贡。

UIEvent是UIKit封裝的描述用戶操作類型的對象扒披,可能有touch事件、motion事件圃泡、remote-control事件碟案、press事件等。不同事件在響應(yīng)鏈中處理方式不同颇蜡,這里我們主要分析touch事件的傳遞和處理价说。

用戶點(diǎn)擊手機(jī)屏幕的過程

App外:用戶點(diǎn)擊->硬件響應(yīng)->參數(shù)量化->數(shù)據(jù)轉(zhuǎn)發(fā)->App接收。

在用戶觸摸屏幕之后风秤,屏幕硬件會接受用戶的操作熔任,并采集關(guān)鍵的參數(shù)傳遞給IOKit,而IOKit將這些數(shù)據(jù)打包并傳給SpringBoard.app唁情,繼而轉(zhuǎn)發(fā)給前臺App。

App內(nèi):子線程接收事件->主線程封裝事件->UIWindow啟動hitTest確定目標(biāo)視圖->UIApplication開始發(fā)送事件->touch事件開始回調(diào)甫匹。

App啟動時(shí)便會啟動一個(gè)com.apple.uikit.eventfetch-thread子線程甸鸟,負(fù)責(zé)接收SpringBoard.app轉(zhuǎn)發(fā)過來的數(shù)據(jù)(通過runloop監(jiān)聽source1,查看堆棧中有__CFRunLoopDoSource1)兵迅,數(shù)據(jù)會被封裝成IOHIDEvent對象抢韭,然后轉(zhuǎn)發(fā)給主線程;

主線程同樣在啟動時(shí)監(jiān)聽source0恍箭,接收eventfetch-thread線程發(fā)送的IOHIDEvent數(shù)據(jù)刻恭,再封裝成UIEvent,根據(jù)UIEvent的類型判斷是否需要啟動hitTest扯夭。motion事件不需要hitTest鳍贾,touch事件也有部分不需要hitTest,比如說touch結(jié)束觸發(fā)的事件交洗。

確定目標(biāo)視圖之后骑科,UIApplication便會發(fā)送事件,將UITouch和UIEvent發(fā)送給目標(biāo)視圖构拳,觸發(fā)其touches系列的方法咆爽。

UIKit尋找目標(biāo)視圖的過程

尋找的過程主要依賴兩個(gè)UIView的方法:-hitTest:withEvent方法和-pointInsdie:withEvent方法梁棠。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

hitTest方法返回point和event對應(yīng)的視圖;

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event

pointInside方法返回point和event是否在自己當(dāng)前視圖上斗埂;

這兩個(gè)方法UIView都提供了默認(rèn)實(shí)現(xiàn)符糊,hitTest方法默認(rèn)會調(diào)用所有子視圖的hitTest方法,如果有一個(gè)返回呛凶。

UIKit會從UIWindow開始尋找目標(biāo)視圖男娄,先調(diào)用UIWindow的hitTest方法詢問是否有響應(yīng)的視圖,hitTest方法首先會先調(diào)用UIWindow的pointInside方法詢問是否在點(diǎn)擊范圍內(nèi)把兔。

a.如果pointInside方法返回NO沪伙,則證明UIWindow無法響應(yīng)該事件,hitTest方法會馬上返回nil县好;
b.如果pointInside方法返回YES围橡,則證明UIWindow可以響應(yīng)該事件,hitTest方法會接著調(diào)用UIWindow子視圖的hitTest方法缕贡。

  • b1.如果子視圖hitTest方法如果有返回視圖翁授,則UIWindow的hitTest方法會返回該視圖;
  • b2.如果所有子視圖hitTest方法都沒有返回視圖晾咪,則UIWindow的hitTest方法會返回自己收擦。

UIWindow是UIView的子類,UIView的hitTest方法實(shí)現(xiàn)和上述過程一致谍倦。

思考:
UIView在調(diào)用子視圖hitTest時(shí)塞赂,是先調(diào)用哪些子視圖?

從subview數(shù)組的末尾開始調(diào)用hitTest昼蛀,subview數(shù)組下標(biāo)越小宴猾,視圖層級越低。

UIKit確定目標(biāo)視圖后的過程

當(dāng)UIKit確定目標(biāo)視圖之后叼旋,就會創(chuàng)建UITouch仇哆,UITouch的window屬性和view屬性就是上面過程中的UIWindow和目標(biāo)視圖。

接著UIApplication就會調(diào)用sendEvent:方法夫植,接著UIWindow在sendEvent:方法中會調(diào)用sendTouchesForEvent:方法讹剔,如下圖:

UIWindow的sendTouchesForEvent:方法調(diào)用的是我們熟悉的touches四大方法:
-touchesBegan:withEvent:
-touchesMoved:withEvent:
-touchesEnded:withEvent:
-touchesCancelled:withEvent:
從上一步尋找到的目標(biāo)視圖開始,目標(biāo)視圖會首先被調(diào)用touches方法详民,接著是目標(biāo)視圖的父視圖延欠,再是父視圖的父視圖,如果某個(gè)視圖是ViewController的.view屬性阐斜,還會調(diào)用ViewController的方法衫冻,直到UIWindow、UIApplication谒出、UIApplicationDelegate(我們創(chuàng)建的AppDelegate)隅俘。

下面是官方文檔給出的回調(diào)順序:(Responder chains in an app)

手勢處理發(fā)生在哪一步

手勢(UIGestureRecognizer)是iPhone的重要交互方式邻奠,手勢識別 介紹了手勢是如何識別,甚至可以添加自定義手勢为居。

UIGestureRecognizer同樣有touches系列方法:

手勢處理的發(fā)生時(shí)機(jī)我們可以通過手勢的touchesBegan:withEvent:方法來看碌宴,當(dāng)我們斷點(diǎn)在手勢的touchesBegan方法時(shí),我們看到堆棧:

注意到堆棧中的UIApplication的sendEvent:方法蒙畴,sendEvent是發(fā)生在UIKit尋找目標(biāo)視圖過程之后贰镣。從另外一種角度來思考,touchesBegan方法中會用到UITouch膳凝,而UITouch中的view屬性是目標(biāo)視圖碑隆,所以手勢的處理應(yīng)該也放在UIKit尋找目標(biāo)視圖之后。

當(dāng)手勢的touchesBegan:withEvent:處理完成之后蹬音,便會觸發(fā)目標(biāo)視圖的touchesBegan方法上煤。

但是當(dāng)手勢識別成功之后,默認(rèn)會cancel后續(xù)touch操作著淆,從目標(biāo)視圖開始的響應(yīng)鏈都會收到touchesCancelled方法劫狠,而不是正常的touchesEnded方法,堆棧如下:

這個(gè)行為也可以通過設(shè)置下面的cancelsTouchesInView=NO來避免觸發(fā)touchesCancelled方法永部。

注意到不管是手勢處理開始的touchesBegan方法独泞,還是手勢識別成功后觸發(fā)touchesCancelled方法,堆棧中都有一個(gè)UIGestureEnvironment類苔埋。這是一個(gè)UIKit的私有類懦砂,在網(wǎng)上搜到相關(guān)代碼介紹:

@interface UIGestureEnvironment : NSObject {
    NSMutableArray * _delayedPresses;
    NSMutableArray * _delayedPressesToSend;
    NSMutableArray * _delayedTouches;
    NSMutableArray * _delayedTouchesToSend;
    UIGestureGraph * _dependencyGraph;
    NSMutableArray * _dirtyGestureRecognizers;
    bool  _dirtyGestureRecognizersUnsorted;
    struct __CFRunLoopObserver { } * _gestureEnvironmentUpdateObserver;
    NSMutableSet * _gestureRecognizersNeedingRemoval;
    NSMutableSet * _gestureRecognizersNeedingReset;
    NSMutableSet * _gestureRecognizersNeedingUpdate;
    NSMapTable * _nodesByGestureRecognizer;
    bool  _updateExclusivity;
}

- (void)addGestureRecognizer:(id)arg1;
- (void)addRequirementForGestureRecognizer:(id)arg1 requiringGestureRecognizerToFail:(id)arg2;
- (bool)gestureRecognizer:(id)arg1 requiresGestureRecognizerToFail:(id)arg2;
- (id)init;
- (void)removeGestureRecognizer:(id)arg1;
...

從頭文件的方法聲明,我們可以大概知道這是一個(gè)手勢管理類组橄,手勢的添加孕惜、移除、響應(yīng)都在內(nèi)部完成晨炕。

思考:

1、UIButton的點(diǎn)擊回調(diào)是怎么實(shí)現(xiàn)的毫炉?
2瓮栗、如果給UIButton添加Tap手勢,點(diǎn)擊UIButton的時(shí)候是觸發(fā)UIButton的Tap手勢瞄勾,還是觸發(fā)UIButton的點(diǎn)擊回調(diào)费奸?

總結(jié)

所以綜上三步,我們可以知道整個(gè)流程大概是:

  1. 尋找目標(biāo)視圖:UIApplication->UIWindow->ViewController->View->targetView
  2. 手勢識別:UIGestureEnvironment-> UIGestureRecognizer
  3. 響應(yīng)鏈回調(diào):targetView->Viewd->ViewController->UIWindow->UIApplication

iOS的用戶交互相關(guān)非常復(fù)雜进陡。由于時(shí)間有限愿阐,這里僅僅從事件的傳遞和處理出發(fā),來建立一個(gè)基礎(chǔ)的認(rèn)知趾疚。

附錄

參考文獻(xiàn)

手勢識別 https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/implementing_a_custom_gesture_recognizer/about_the_gesture_recognizer_state_machine

響應(yīng)鏈介紹 https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/using_responders_and_the_responder_chain_to_handle_events?from=from_parent_mindnote

思考題

1缨历、UIButton的點(diǎn)擊回調(diào)是怎么實(shí)現(xiàn)的以蕴?

UIButton是UIControl的子類,通過追蹤touch事件的變化得到一些UIControl定義的事件(UIControlEvents)辛孵;UIButton的點(diǎn)擊操作是通過UIControlEvents的事件變化回調(diào)來觸發(fā)丛肮,本質(zhì)依賴的是響應(yīng)鏈回調(diào)過程中的touches系列方法。

2魄缚、如果給UIButton添加Tap手勢宝与,點(diǎn)擊UIButton的時(shí)候是觸發(fā)UIButton的Tap手勢,還是觸發(fā)UIButton的點(diǎn)擊回調(diào)冶匹?

上文分析了手勢的識別是發(fā)生在響應(yīng)鏈回調(diào)之前习劫,也就是tap手勢是發(fā)生在touches系列方法回調(diào)之前,那么Tap手勢應(yīng)該是在UIButton的touches方法之前嚼隘。如果UIButton監(jiān)聽的是常用的UIControlEventTouchUpInside事件诽里,則不會回調(diào);如果監(jiān)聽的是UIControlEventTouchCancel事件嗓蘑,則在觸發(fā)完Tap手勢之后须肆,還會收到回調(diào)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末桩皿,一起剝皮案震驚了整個(gè)濱河市豌汇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泄隔,老刑警劉巖拒贱,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異佛嬉,居然都是意外死亡逻澳,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門暖呕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來斜做,“玉大人,你說我怎么就攤上這事湾揽∪勘疲” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵库物,是天一觀的道長霸旗。 經(jīng)常有香客問我,道長戚揭,這世上最難降的妖魔是什么诱告? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮民晒,結(jié)果婚禮上精居,老公的妹妹穿的比我還像新娘锄禽。我一直安慰自己,他們只是感情好箱蟆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布沟绪。 她就那樣靜靜地躺著,像睡著了一般空猜。 火紅的嫁衣襯著肌膚如雪绽慈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天辈毯,我揣著相機(jī)與錄音坝疼,去河邊找鬼。 笑死谆沃,一個(gè)胖子當(dāng)著我的面吹牛钝凶,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播唁影,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼耕陷,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了据沈?” 一聲冷哼從身側(cè)響起哟沫,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锌介,沒想到半個(gè)月后嗜诀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡孔祸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年隆敢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情椎麦,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏誉结。R本人自食惡果不足惜鹅士,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望惩坑。 院中可真熱鬧掉盅,春花似錦也拜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至永票,卻和暖如春卵贱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背侣集。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工键俱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人世分。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓编振,卻偏偏與公主長得像,于是被迫代替她去往敵國和親臭埋。 傳聞我的和親對象是個(gè)殘疾皇子踪央,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 在開發(fā)過程中,大家或多或少的都會碰到令人頭疼的手勢沖突問題瓢阴,正好前兩天碰到一個(gè)類似的bug畅蹂,于是借著這個(gè)機(jī)會了解了...
    閆仕偉閱讀 5,333評論 2 23
  • 該文章屬于劉小壯原創(chuàng),轉(zhuǎn)載請注明:劉小壯[http://www.reibang.com/u/2de707c93d...
    劉小壯閱讀 32,085評論 32 209
  • 事件的生命周期 當(dāng)指尖觸碰屏幕的那一刻炫掐,一個(gè)觸摸事件就在系統(tǒng)中生成了魁莉。經(jīng)過IPC進(jìn)程間通信,事件最終被傳遞到了合適...
    HughKaun閱讀 1,184評論 0 4
  • 1.3事件的傳遞和處理 (一)事件的產(chǎn)生和傳遞 事件傳遞的作用就是找到合適的view來處理事件 1.當(dāng)發(fā)生觸摸事件...
    劉2傻閱讀 322評論 0 2
  • 在使用手機(jī)的過程中募胃,會產(chǎn)生很多交互事件旗唁,如觸摸屏幕、搖晃痹束、按下按鍵检疫、使用耳機(jī)操控設(shè)備等。這些事件都需要系統(tǒng)去響應(yīng)并...
    pro648閱讀 584評論 1 2