iOS開發(fā)透徹理解事件響應(yīng)

很多文章都講了關(guān)于事件響應(yīng)的話題讥电,但是我們是不是真正明白了事件是怎么尋找和怎么響應(yīng)的,還是這些文章僅僅在介紹以下兩個(gè)函數(shù)呢欣福?

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

當(dāng)然,這兩個(gè)函數(shù)也重要语泽,但是僅僅是其中的一部分

當(dāng)我們的手指點(diǎn)擊到屏幕上的時(shí)候贸典,一系列的操作開始了;

當(dāng)然踱卵,前半部分是操作系統(tǒng)做了很多工作瓤漏,涉及到硬件的相關(guān)操作,包括IOKit.framework 生成一個(gè) IOHIDEvent 事件颊埃,SpringBoard(屏幕管理)接收蔬充,然后進(jìn)行進(jìn)程的分發(fā),這些可以稍作了解班利,然后就是進(jìn)入到我們的程序中饥漫,我們的程序啟動(dòng)的時(shí)候,會(huì)注冊(cè)一個(gè)Source1罗标,通過port接收這些分發(fā)過來的事件庸队,收到觸發(fā)以后,回調(diào)這個(gè)函數(shù)闯割,__IOHIDEventSystemClientQueueCallback()彻消,在__IOHIDEventSystemClientQueueCallback()內(nèi)觸發(fā)的Source0,
Source0再觸發(fā)的 _UIApplicationHandleEventQueue()宙拉,到達(dá)事件隊(duì)列以后宾尚,IOHIDEvent在之前就被轉(zhuǎn)換成了Event事件,然后進(jìn)行事件的分發(fā)和響應(yīng)處理谢澈。

自己對(duì)事件的響應(yīng)過程做了區(qū)分煌贴,尋找響應(yīng)者,和具體觸發(fā)響應(yīng)兩個(gè)過程锥忿,這兩個(gè)過程中牛郑,涉及到一些細(xì)節(jié)需要注意。

尋找響應(yīng)者:

從當(dāng)前的window開始敬鬓,向外遍歷子視圖淹朋,尋找能響應(yīng)事件的view,這時(shí)候有一個(gè)點(diǎn)要注意钉答,在iOS系統(tǒng)中础芍,只有繼承于UIResponder的類,才能響應(yīng)事件希痴,這個(gè)類的內(nèi)部有touchesBegan 者甲、touchesEnded等四個(gè)方法春感,可以反向理解砌创,只有實(shí)現(xiàn)這幾個(gè)方法的類才能響應(yīng)事件虏缸,UIView、UIViewController嫩实、UIWindow都是繼承于這個(gè)類的刽辙。
尋找的過程不做細(xì)究,其他文章已經(jīng)寫得很好了甲献,通過一下兩個(gè)函數(shù):
-- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
-(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
來確定響應(yīng)者然后return

響應(yīng):

到了這一步宰缤,僅僅完成階段一,然后下一步才是真正的響應(yīng)過程晃洒,上邊提到慨灭,繼承于UIResponder的類才能響應(yīng)事件,但是在系統(tǒng)中球及,默認(rèn)能響應(yīng)事件的都是UIControl的子類氧骤,UIButton這些都是繼承于UIControl的。
這地方涉及到一個(gè)細(xì)節(jié)就是吃引,雖然繼承于UIResponder的類筹陵,都是實(shí)現(xiàn)了touchesBegan 、touchesEnded等四個(gè)方法镊尺,但是朦佩,他們內(nèi)部默認(rèn)的實(shí)現(xiàn)都是[self.nextResponder touchesBegin],也就是交個(gè)上一級(jí)的響應(yīng)者去響應(yīng)庐氮,只有UIControl是特例语稠,他們?cè)趖ouchesBegan實(shí)現(xiàn)了判斷響應(yīng)的過程,并且阻斷了事件的繼續(xù)向上傳遞弄砍,在UIControl的touchesBegan實(shí)現(xiàn)偽代碼如下

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [touches anyObject];
    _touchInside = YES;
    _tracking = [self beginTrackingWithTouch:touch withEvent:event];

    self.highlighted = YES;  //高亮設(shè)置

    if (_tracking) {
        UIControlEvents currentEvents = UIControlEventTouchDown;

        if (touch.tapCount > 1) {
            currentEvents |= UIControlEventTouchDownRepeat;
        }

        [self _sendActionsForControlEvents:currentEvents withEvent:event];
    }
}

- (void)_sendActionsForControlEvents:(UIControlEvents)controlEvents withEvent:(UIEvent *)event
{
    for (UIControlAction *controlAction in _registeredActions) {
        if (controlAction.controlEvents & controlEvents) {
            [self sendAction:controlAction.action to:controlAction.target forEvent:event];
        }
    }
}

- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
    [[UIApplication sharedApplication] sendAction:action to:target from:self forEvent:event];
}
根據(jù)這三個(gè)函數(shù)颅筋,就能了解,事件在UIControl中的響應(yīng)過程了输枯,并且是怎么被阻斷的(因?yàn)楦緵]有向后傳遞耙楸谩)。


這是sendAction函數(shù)內(nèi)部的偽代碼
   if (target) {
        typedef void(*EventActionMethod)(id, SEL, id, UIEvent *);
        EventActionMethod method = (EventActionMethod)[target methodForSelector:action];
        method(target, action, sender, event);
        return YES;
    }
直接就是函數(shù)指針的調(diào)用

注意:也不是到了UIControl就一定會(huì)響應(yīng)桃熄,你注冊(cè)button的時(shí)候先口,會(huì)設(shè)置點(diǎn)擊的type,單擊瞳收、雙擊碉京、長(zhǎng)按等,響應(yīng)的時(shí)候也會(huì)匹配過來的Event中的touch事件是不是和注冊(cè)的一致螟深,如果不一致谐宙,就不會(huì)響應(yīng)。

除了系統(tǒng)的這些UIControl還能怎么響應(yīng)系統(tǒng)的事件呢界弧,仿照UIControl去操作凡蜻,直接重寫touchesBegin方法搭综,在里邊進(jìn)行響應(yīng)的處理。


我們知道划栓,響應(yīng)時(shí)間除了UIControl這些兑巾,還有一類就是手勢(shì),手勢(shì)是單獨(dú)處理的忠荞,他的優(yōu)先級(jí)高于UIControl的蒋歌,那他的尋找響應(yīng)者的過程和響應(yīng)的過程又是怎么樣的呢?

當(dāng)尋找到能響應(yīng)事件的view以后委煤,不會(huì)馬上執(zhí)行touchesEnd方法堂油,而是查看有沒有手勢(shì)手勢(shì)事件,如果有手勢(shì)事件會(huì)碧绞,調(diào)用touchesCancel称诗,取消掉UIControl的touchEvent事件,去響應(yīng)手勢(shì)的點(diǎn)擊或者其他事件头遭。

注意:手勢(shì)的響應(yīng)不是立刻執(zhí)行的寓免,他的回調(diào)是在runloop睡眠之前執(zhí)行的,可以打印一下main runloop看一下计维,里邊專門有一個(gè)observer是干這個(gè)的袜香。

還有一個(gè)注意點(diǎn),就是UIControl在判斷響應(yīng)的時(shí)候鲫惶,是會(huì)拿出touch對(duì)象中的view比對(duì)是不是self蜈首,如果是才會(huì)響應(yīng)。
if ([touch view] == self) { //TODO: UIControlEvents中定義的事件的識(shí)別邏輯 [self beginTrackingWithTouch:touch withEvent:event]; }
也就是說欠母,你在button上欢策,蓋一層view。理論上來說赏淌,view會(huì)將響應(yīng)向上傳遞踩寇,但是,傳遞給button的時(shí)候六水,他發(fā)現(xiàn)俺孙,touch的view不是button,就不會(huì)響應(yīng)掷贾【﹂可能在開發(fā)中會(huì)遇到這個(gè)問題。


輔助理解想帅,很多人可能對(duì)touch對(duì)象的內(nèi)部不是很清楚场靴,下邊是UITouch的內(nèi)部實(shí)現(xiàn),通過這個(gè),就可以幫助理解怎么找到哪個(gè)window響應(yīng)旨剥、手勢(shì)咧欣、響應(yīng)的view相關(guān)。

UIKIT_EXTERN API_AVAILABLE(ios(2.0)) @interface UITouch : NSObject

@property(nonatomic,readonly) NSTimeInterval      timestamp;
@property(nonatomic,readonly) UITouchPhase        phase;
@property(nonatomic,readonly) NSUInteger          tapCount;   // touch down within a certain point within a certain amount of time
@property(nonatomic,readonly) UITouchType         type API_AVAILABLE(ios(9.0));

// majorRadius and majorRadiusTolerance are in points
// The majorRadius will be accurate +/- the majorRadiusTolerance
@property(nonatomic,readonly) CGFloat majorRadius API_AVAILABLE(ios(8.0));
@property(nonatomic,readonly) CGFloat majorRadiusTolerance API_AVAILABLE(ios(8.0));

@property(nullable,nonatomic,readonly,strong) UIWindow                        *window;
@property(nullable,nonatomic,readonly,strong) UIView                          *view;
@property(nullable,nonatomic,readonly,copy)   NSArray <UIGestureRecognizer *> *gestureRecognizers
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末泞边,一起剝皮案震驚了整個(gè)濱河市该押,隨后出現(xiàn)的幾起案子疗杉,更是在濱河造成了極大的恐慌阵谚,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,949評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烟具,死亡現(xiàn)場(chǎng)離奇詭異梢什,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)朝聋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,772評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門嗡午,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人冀痕,你說我怎么就攤上這事荔睹。” “怎么了言蛇?”我有些...
    開封第一講書人閱讀 158,419評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵僻他,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我腊尚,道長(zhǎng)吨拗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,812評(píng)論 1 285
  • 正文 為了忘掉前任婿斥,我火速辦了婚禮劝篷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘民宿。我一直安慰自己娇妓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,927評(píng)論 6 386
  • 文/花漫 我一把揭開白布活鹰。 她就那樣靜靜地躺著峡蟋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪华望。 梳的紋絲不亂的頭發(fā)上蕊蝗,一...
    開封第一講書人閱讀 50,102評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音赖舟,去河邊找鬼蓬戚。 笑死,一個(gè)胖子當(dāng)著我的面吹牛宾抓,可吹牛的內(nèi)容都是我干的子漩。 我是一名探鬼主播豫喧,決...
    沈念sama閱讀 39,171評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼幢泼!你這毒婦竟也來了紧显?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,921評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤缕棵,失蹤者是張志新(化名)和其女友劉穎孵班,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體招驴,經(jīng)...
    沈念sama閱讀 44,366評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡篙程,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,675評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了别厘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片虱饿。...
    茶點(diǎn)故事閱讀 38,820評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖触趴,靈堂內(nèi)的尸體忽然破棺而出氮发,到底是詐尸還是另有隱情,我是刑警寧澤冗懦,帶...
    沈念sama閱讀 34,523評(píng)論 4 335
  • 正文 年R本政府宣布爽冕,位于F島的核電站,受9級(jí)特大地震影響批狐,放射性物質(zhì)發(fā)生泄漏扇售。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,162評(píng)論 3 317
  • 文/蒙蒙 一嚣艇、第九天 我趴在偏房一處隱蔽的房頂上張望承冰。 院中可真熱鬧,春花似錦食零、人聲如沸困乒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,885評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽娜搂。三九已至,卻和暖如春吱抚,著一層夾襖步出監(jiān)牢的瞬間百宇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,126評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工秘豹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留携御,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,647評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像啄刹,于是被迫代替她去往敵國(guó)和親涮坐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,732評(píng)論 2 351