目录
介绍
一、介绍
run loop 是一个事件处理循环,用来不停的调度工作以及处理输入事件。
RunLoop的基本作用
保持程序的持续运行
处理App中的各种事件(比如触摸事件、定时器事件等)
节省CPU资源,提高程序性能:该做事时做事,该休息时休息
二、应用范畴
定时器(Timer)、PerformSelector
GCD Async Main Queue
事件响应、手势识别、界面刷新
网络请求
AutoreleasePool
三、实际开发中的应用
控制线程生命周期(线程保活)
解决NSTimer在滑动时停止工作的问题
监控应用卡顿
性能优化
四、runloop的驱动
你的代码要提供实现循环部分的控制语句,换言之就是要有 while 或 for 循环语句来驱动 run loop。
runloop与线程
每条线程都有唯一的一个与之对应的RunLoop对象
RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
RunLoop会在线程结束时销毁
主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
runloop相关类
一个RunLoop包含若干个Mode
RunLoop启动时只能选择其中一个Mode,作为currentMode
每个Mode又包含若干个Source0/Source1/Timer/Observer
请注意__CFRunLoopMode下的属性都是set 和 array
一、CFRunLoopModeRef
1、介绍 CFRunLoopModeRef代表RunLoop的运行模式
RunLoop启动时只能选择其中一个Mode,作为currentMode
如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入
不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
2、常见的Mode
kCFRunLoopDefaultMode:NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
NSRunLoopCommonModes:不是一个具体的模式,代表多个模式的集合
二、CFRunLoopObserverRef
注册的 run loop 观察者(run-loop Observers)可以收到这些通知,并在线程上面使用它们来做额外的处理。
run loop 观察者可以只用一次或循环使用。若只用一次,那么在 它启动后,会把它自己从 run loop 里面移除,而循环的观察者则不会。你在创建 run loop 观察者的时候需要指定它是运行一次还是多次。
void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
default:
break;
}
}
// 创建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放
CFRelease(observer);
runloop的源
一、输入源
Port(端口): 是用于在不同线程或进程之间传递消息的通信端点。
在这里,Mach Port 用于在应用程序和系统内核之间传递事件消息。
Source1 : 监听Mach Port(基于port的),并触发相应的回调函数处理事件
Source0 : 非基于port的,通过监听触摸事件、performSelector等方式触发,通过回调函数处理相关事件
二、定时源
runloop与定时器的关系,gcd定时器为什么精准
runloop的运行逻辑
一、runloop的唤醒
从文章开头的参考文章中提供的runloop的源码中可以看出,runloop唤醒后处理的事件中没有source0
if (被Timer唤醒) {
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())
} else if (被GCD唤醒) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg)
} else {
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, $reply)
}
__CFRunLoopDoBlocks(rl, rlm)
xy:也就是说只有系统事件可以唤醒runloop。那么为什么我们点击屏幕时打印调用栈时,看到的是__CFRunLoopDoSource0
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
* frame #0: 0x0000000108cf8ac5 XYAppMain`-[XYHomeViewController touchesBegan:withEvent:](self=0x00007fb944a263b0, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x0000600002594000) at XYHomeViewController.m:24:5
frame #1: 0x000000011f8aa2fa UIKitCore`forwardTouchMethod + 312
frame #2: 0x000000011f8bcd8f UIKitCore`-[UIWindow _sendTouchesForEvent:] + 617
frame #3: 0x000000011f8bf094 UIKitCore`-[UIWindow sendEvent:] + 5310
frame #4: 0x000000011f892782 UIKitCore`-[UIApplication sendEvent:] + 898
frame #5: 0x000000011f939a42 UIKitCore`__dispatchPreprocessedEventFromEventQueue + 7367
frame #6: 0x000000011f93c8c7 UIKitCore`__processEventQueue + 8444
frame #7: 0x000000011f932bc7 UIKitCore`__eventFetcherSourceCallback + 161
frame #8: 0x000000010a181b8f CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #9: 0x000000010a181ad1 CoreFoundation`__CFRunLoopDoSource0 + 157
frame #10: 0x000000010a1812cd CoreFoundation`__CFRunLoopDoSources0 + 217
frame #11: 0x000000010a17b9ba CoreFoundation`__CFRunLoopRun + 889
frame #12: 0x000000010a17b264 CoreFoundation`CFRunLoopRunSpecific + 560
frame #13: 0x0000000112cea24e GraphicsServices`GSEventRunModal + 139
frame #14: 0x000000011f8717bf UIKitCore`-[UIApplication _run] + 994
frame #15: 0x000000011f8765de UIKitCore`UIApplicationMain + 123
frame #16: 0x000000010039fd0d XYApp`main(argc=1, argv=0x0000000304b15c78) at main.m:11:12
frame #17: 0x0000000108a09384 dyld_sim`start_sim + 10
frame #18: 0x0000000200688310 dyld`start + 2432
参考文章:iOS触摸点击事件之runloop做了什么?
点击屏幕 - springboard进程捕获点击事件 - 通过端口传给当前进程 -
source1检测到事件唤醒runloop,调用source1的回调 - 在处理source1的回调中触发source0 -
source0再触发_UIApplicationHandleEventQueue() - UIWindow - SubView - SubView
二、Runloop 会在以下几种情况下退出:
手动调用 CFRunLoopStop 函数,停止 Runloop。
所有输入源和定时器都被移除。
Runloop 遇到错误。
退出时,Runloop 会发送 kCFRunLoopExit 通知给观察者,观察者的回调函数将被执行
主线程的runloop在哪启动
UIApplicationMain函数内启动了Runloop,程序不会马上退出,而是保持运行状态。因此每一个应用必须要有一个runloop,我们知道主线程一开起来,就会跑一个和主线程对应的RunLoop,那么RunLoop一定是在程序的入口main函数中开启。
运行程序,我们发现只会打印开始,并不会打印结束,这说明在UIApplicationMain函数中,开启了一个和主线程相关的RunLoop,导致UIApplicationMain不会返回,一直在运行中,也就保证了程序的持续运行。
我们来看到RunLoop的源码
我们发现RunLoop确实是do while通过判断result的值实现的。因此,我们可以把RunLoop看成一个死循环。如果没有RunLoop,UIApplicationMain函数执行完毕之后将直接返回,也就没有程序持续运行一说了。
行者常至,为者常成!