iOS开发透彻理解事件响应

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

- (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  *gestureRecognizers

你可能感兴趣的:(iOS开发透彻理解事件响应)