前言
UIResponder类继承NSObject,iOS中所有继承自UIResponder的类的实例, 都是可以响应touch事件的对象。在iOS体系中,UIApplication、UIViewController和UIView是直接继承UIResponder类的,所以它们能够直接响应事件。特别的是UIWindow继承UIView所以UIWindow也是可以响应事件。重写UIResponder 提供的方法来完成特定的需求。响应机制分为传递链和响应链.
传递链: 由系统向离用户最近的view传递: UIApplication -> UIWindow -> RootViewController -> View -> ... -> Button
响应链: 由离用户最近的view向系统响应: Button -> View -> ... -> RootViewController -> UIWindow -> UIApplication
RunLoop在程序的响应扮演重要角色。简单说UIApplicationMain会执行一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer。后文有详细介绍。
响应事件
iOS中的事件分为触摸事件、运动事件、远程控制事件和物理按压事件。
UIEventTypeTouches: 触摸手机屏幕事件
UIEventTypeMotion:手机的摇晃和运动事件。比如摇晃手机,手机陀螺仪感应,该事件由UIKit触发的,因此它不遵守事件响应机制。
UIEventTypeRemoteControl:手机远程控制事件。主要是用来接收耳机等外部设备的命令,目的是用来控制手机的多媒体。
UIEventTypePresses: 物理按压事件,比如音量、开关物理键。
当点击屏幕后, 发生了什么?
事件的传递
当用户触摸屏幕后,RunLoop的source1会将该触摸事件捕捉,source1把它包装成事件放到事件队列EventQueue里。再包分发到source0来处理。
source0会从事件队列中取出最前面的事件,并将事件分发下去以便处理,通常会先发送事件给应用程序的主窗口(keyWindow)。主窗口会在视图层次结构中找到一个最合适的视图来处理触摸事件。
结果找到了最合适接收事件的Button。而事件响应是从Button开始的。
如下图:事件传递遵循自父视图到子视图的顺序,从左到右就是为传递链。图中从右向左为响应链.

事件的响应
如果当前的响应控件不处理事件,那么该事件沿着响应链向上传递,如果响应该事件,则消费该事件,停止在响应链上传递。如果响应链传到UIApplication还没被处理就丢弃。它与查找最合适View的方向相反。

处理-休眠-结束休眠-处理
我们代码是逐行执行的,为什么iOS完成了任务后并没有退出程序而是继续休眠,执行新任务呢。因为系统加入RunLoop机制。相当于我们执行了一个do-while循环。
int main(int argc, char * argv[]) {
@autoreleasepool {
do{}while (0 == retval);
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
RunLoop的基本作用
1 保持程序的持续运行
2 处理App中的各种事件(比如触摸事件、定时器事件等)
3 节省CPU资源,提高程序性能:该做事时做事,该休息时休息
RunLoop对象
iOS中有2套API来访问和使用RunLoop
1 Foundation:NSRunLoop
2 Core Foundation:CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表着RunLoop对象
1 NSRunLoop是基于CFRunLoopRef的一层OC包装
2 CFRunLoopRef是开源的
https://opensource.apple.com/tarballs/CF/
RunLoop与线程
1 每条线程都有唯一的一个与之对应的RunLoop对象
2 RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
3 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
4 RunLoop会在线程结束时销毁
5 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
获取RunLoop对象
Foundation
1 [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
2 [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
Core Foundation
1 CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
2 CFRunLoopGetMain(); // 获得主线程的RunLoop对象
RunLoop相关的类
Core Foundation中关于RunLoop的5个类
1 CFRunLoopRef
2 CFRunLoopModeRef
3 CFRunLoopSourceRef
4 CFRunLoopTimerRef
5 CFRunLoopObserverRef
CFRunLoopModeRef
1 CFRunLoopModeRef代表RunLoop的运行模式
2 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
3 RunLoop启动时只能选择其中一个Mode,作为currentMode
4 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入
不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
5 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
CFRunLoopModeRef
常见的2种Mode
1 kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
2 UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
RunLoop的运行逻辑
Source0
1 触摸事件处理 (屏幕点击时间由source1捕捉,再包分发到source0来处理)
2 performSelector:onThread:
Source1
1 基于Port的线程间通信
2 系统事件捕捉
Timers
1 NSTimer
2 performSelector:withObject:afterDelay:
Observers
1 用于监听RunLoop的状态
2 UI刷新(BeforeWaiting) //监听器,UI界面的刷新由监听器实现,监听器监听到runloop要休眠,再睡觉前刷新界面.
3 Autorelease pool(BeforeWaiting) //监听到要睡觉,再睡觉前清理一下需要自动释放的对象
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry"); //进入某种mode
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting: //休眠
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting: //唤醒
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit: //离开某种mode
NSLog(@"kCFRunLoopExit");
break;
default:
break;
01、通知Observers:进入Loop
02、通知Observers:即将处理Timers
03、通知Observers:即将处理Sources
04、处理Blocks
05、处理Source0(可能会再次处理Blocks)
06、如果存在Source1,就跳转到第8步
07、通知Observers:开始休眠(等待消息唤醒)
08、通知Observers:结束休眠(被某个消息唤醒)
01> 处理Timer
02> 处理GCD Async To Main Queue
03> 处理Source1
09、处理Blocks
10、根据前面的执行结果,决定如何操作
01> 回到第02步
02> 退出Loop
11、通知Observers:退出Loop
RunLoop在实际开中的应用
1 控制线程生命周期(线程保活)
2 解决NSTimer在滑动时停止工作的问题
3 监控应用卡顿
4 性能优化
备注
1 传入通用模式kCFRunLoopCommonModes,代表着同时监听NSDefaultRunLoopMode(kCFRunLoopDefaultMode)模式和UITrackingRunLoopMode模式
2 写的代码都是runloop分发下去执行的吗?是的背景色也好,点击事件也好,定时器也好,都是由runloop决定什么时候去执行.
3 GCD很多流程不依赖runnloop, 处理GCD Async To Main Queue
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 处理一些子线程的逻辑
// 回到主线程去刷新UI界面
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"");
});
});
4 NSDefaultRunLoopMode、UITrackingRunLoopMode才是真正存在的模式
NSRunLoopCommonModes并不是一个真的模式,它只是一个标记
timer能在_commonModes数组中存放的模式下工作
5 AFNetworking 通过runloop来控制子线程的生命周期,他创建一个子线程,让子线程一直在内存中.在经常要在子线程中做事情的时候有好处.避免频繁创建子线程.
不断创建和销毁很耗性能.
应用范畴
1 定时器(Timer)、PerformSelector
2 GCD Async Main Queue
3 事件响应、手势识别、界面刷新
4 网络请求
5 AutoreleasePool