美文网首页
iOS开发透彻理解事件响应

iOS开发透彻理解事件响应

作者: RainyHand | 来源:发表于2020-05-11 15:38 被阅读0次

    很多文章都讲了关于事件响应的话题,但是我们是不是真正明白了事件是怎么寻找和怎么响应的,还是这些文章仅仅在介绍以下两个函数呢?

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

    当然,这两个函数也重要,但是仅仅是其中的一部分

    当我们的手指点击到屏幕上的时候,一系列的操作开始了;

    当然,前半部分是操作系统做了很多工作,涉及到硬件的相关操作,包括IOKit.framework 生成一个 IOHIDEvent 事件,SpringBoard(屏幕管理)接收,然后进行进程的分发,这些可以稍作了解,然后就是进入到我们的程序中,我们的程序启动的时候,会注册一个Source1,通过port接收这些分发过来的事件,收到触发以后,回调这个函数,__IOHIDEventSystemClientQueueCallback(),在__IOHIDEventSystemClientQueueCallback()内触发的Source0,
    Source0再触发的 _UIApplicationHandleEventQueue(),到达事件队列以后,IOHIDEvent在之前就被转换成了Event事件,然后进行事件的分发和响应处理。

    自己对事件的响应过程做了区分,寻找响应者,和具体触发响应两个过程,这两个过程中,涉及到一些细节需要注意。

    寻找响应者:

    从当前的window开始,向外遍历子视图,寻找能响应事件的view,这时候有一个点要注意,在iOS系统中,只有继承于UIResponder的类,才能响应事件,这个类的内部有touchesBegan 、touchesEnded等四个方法,可以反向理解,只有实现这几个方法的类才能响应事件,UIView、UIViewController、UIWindow都是继承于这个类的。
    寻找的过程不做细究,其他文章已经写得很好了,通过一下两个函数:
    -- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
    -(BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;
    来确定响应者然后return

    响应:

    到了这一步,仅仅完成阶段一,然后下一步才是真正的响应过程,上边提到,继承于UIResponder的类才能响应事件,但是在系统中,默认能响应事件的都是UIControl的子类,UIButton这些都是继承于UIControl的。
    这地方涉及到一个细节就是,虽然继承于UIResponder的类,都是实现了touchesBegan 、touchesEnded等四个方法,但是,他们内部默认的实现都是[self.nextResponder touchesBegin],也就是交个上一级的响应者去响应,只有UIControl是特例,他们在touchesBegan实现了判断响应的过程,并且阻断了事件的继续向上传递,在UIControl的touchesBegan实现伪代码如下

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        UITouch *touch = [touches anyObject];
        _touchInside = YES;
        _tracking = [self beginTrackingWithTouch:touch withEvent:event];
    
        self.highlighted = YES;  //高亮设置
    
        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];
    }
    根据这三个函数,就能了解,事件在UIControl中的响应过程了,并且是怎么被阻断的(因为根本没有向后传递啊)。
    
    
    这是sendAction函数内部的伪代码
       if (target) {
            typedef void(*EventActionMethod)(id, SEL, id, UIEvent *);
            EventActionMethod method = (EventActionMethod)[target methodForSelector:action];
            method(target, action, sender, event);
            return YES;
        }
    直接就是函数指针的调用
    

    注意:也不是到了UIControl就一定会响应,你注册button的时候,会设置点击的type,单击、双击、长按等,响应的时候也会匹配过来的Event中的touch事件是不是和注册的一致,如果不一致,就不会响应。

    除了系统的这些UIControl还能怎么响应系统的事件呢,仿照UIControl去操作,直接重写touchesBegin方法,在里边进行响应的处理。


    我们知道,响应时间除了UIControl这些,还有一类就是手势,手势是单独处理的,他的优先级高于UIControl的,那他的寻找响应者的过程和响应的过程又是怎么样的呢?

    当寻找到能响应事件的view以后,不会马上执行touchesEnd方法,而是查看有没有手势手势事件,如果有手势事件会,调用touchesCancel,取消掉UIControl的touchEvent事件,去响应手势的点击或者其他事件。

    注意:手势的响应不是立刻执行的,他的回调是在runloop睡眠之前执行的,可以打印一下main runloop看一下,里边专门有一个observer是干这个的。

    还有一个注意点,就是UIControl在判断响应的时候,是会拿出touch对象中的view比对是不是self,如果是才会响应。
    if ([touch view] == self) { //TODO: UIControlEvents中定义的事件的识别逻辑 [self beginTrackingWithTouch:touch withEvent:event]; }
    也就是说,你在button上,盖一层view。理论上来说,view会将响应向上传递,但是,传递给button的时候,他发现,touch的view不是button,就不会响应。可能在开发中会遇到这个问题。


    辅助理解,很多人可能对touch对象的内部不是很清楚,下边是UITouch的内部实现,通过这个,就可以帮助理解怎么找到哪个window响应、手势、响应的view相关。

    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
    

    相关文章

      网友评论

          本文标题:iOS开发透彻理解事件响应

          本文链接:https://www.haomeiwen.com/subject/rftunhtx.html