第一响应者 (The First Responder)
什么是第一响应者?简单的讲,第一响应者是一个UIWindow对象接收到一个事件后,第一个来响应的该事件的对象。注意:这个第一响应者与触摸检测到的第一个响应的UIView并不是一个概念。第一响应者一般情况下用于处理非触摸事件(手机摇晃、耳机线控的远程空间)或非本窗口的触摸事件(键盘触摸事件),通俗点讲其实就是管别人闲事的响应者。在IOS中,当然管闲事并不是所有控件都愿意的,这么说好像并不是很好理解,或着是站在编程人员的角度来看待这个问题,程序员负责告诉系统哪个对象可以成为第一响应者(canBecomeFirstResponder),如果方法canBecomeFirstResponder返回YES,这个响应者对象才有资格称为第一响应者。有资格并不代表一定可以成为第一响应者,还要becomeFirstResponder正式成为第一响应者。同时也有对应的canResignFirstResponder和resignFirstResponder是否可以解除第一响应者。
值得注意的是,一个UIWindow对象在某一时刻只能有一个响应者对象可以成为第一响应者。我们可以通过isFirstResponder来判断某一个对象是否为第一响应者。
我们可以用苹果的私有方法来获得第一响应者
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView *firstResponder = [keyWindow performSelector:@selector(firstResponder)];
下面我们用摇一摇事件来说一说第一响应者响应事件流程
代码
在视图控制器中
- (void)viewDidLoad {
[super viewDidLoad];
HeaderView *headerView = [[HeaderView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 200)];
headerView.backgroundColor = [UIColor redColor];
[self.view addSubview:headerView];
}
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
NSLog(@"~~~~~~~~~motionBegan");
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView *firstResponder = [keyWindow performSelector:@selector(firstResponder)];
}
在晃动手机后,我们可以看到断点打印的firstResponder为空。
此时的调用流程为:
这个时候却响应了摇一摇事件,为什么呢?系统这个时候会找到当前视图,即self.view,如果self.view如果不能响应,则会依据响应链找到能够响应该事件的响应者,在这里self.view.nextResponse就是ViewController,所以会响应这个摇一摇事件。
然后我们在HerderView里面加入一些代码
@interface HeaderView ()
@end
@implementation HeaderView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// [self becomeFirstResponder];
}
return self;
}
//- (BOOL)canBecomeFirstResponder {
//
// return YES;
//}
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow];
UIView *firstResponder = [keyWindow performSelector:@selector(firstResponder)];
}
然后摇一摇事件,发现HeaderView并没有响应摇一摇事件,而是还是由ViewController来响应的。
然后我们将注释的部分打开,注意这里让HeaderView成为响应者,注意一定要写canBecomeFirstResponder函数,不然HeaderView就算写了becomeFirstResponder也不能成为第一响应者,然后摇一摇可以看到HeaderView的motionBegan调用了,HeaderView响应了这个事件,同时这个时候的第一响应者为HeaderView,函数调用流程为:
然后将HeaderView的motionBegan函数注释掉,摇一摇,然后发现ViewController的motionBegan函数调用了,这个时候事件由第一响应者HeaderView传递给了ViewController,事件的调用流程:
在这里我们看到调用流程里有forwardMethodIntld函数,推测他就是在响应链中的响应者无法响应事件时进行转发到下一个响应者的函数,
在这里我们可以分析一下
- 在HeaderView没有成为第一响应者,摇一摇的时候有一个forwardMethodIntld函数,这个时候由self.view进行开始响应,但是self.view并不能响应该事件,然后向上传递给ViewController,而ViewController能够响应事件,然后事件到这里被处理,不会继续传递下去,所以进行了一次转发。
- 在HeaderView成为第一响应者,然后也有摇一摇事件motionBegan响应的时候,事件直接找到第一响应者,发现第一响应者能够响应该事件,事件到此结束,所以这里没有forwardMethodIntld函数,也就没有进行事件的转发。
- 在HeaderView成为第一响应者,然后没有响应摇一摇事件motionBegan的时候,ViewController响应了该事件,发现这里有两个forwardMethodIntld函数,原因是找到第一响应者HeaderView的时候,发现并不能响应该事件,然后由响应链的上一层self.view响应,self.view也不能响应该事件,然后找到self.view的上一层,即ViewController,然后响应该事件,所以这里进行了两次事件的转发。