iOS Runloop 基础
1. 一个 runloop 可以包含有很多个 mode
2. 每个 mode 可以有多个 source0 / source1 / Timer / Observer
3. runloop 启动的时候只能对应一个 mode
4. 切换 runloop 的时候,需要退出当前 runloop ,然后重新选择一个新的。
5. 不同 mode 下的 source0/source1/timer/observer ,互不影响,比如 tableView在滚动时候的mode不影响正常情况下的mode事件。
6. 如果mode里面没有source0/source1/timer/observer,会退出
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),// 即将进入 runloop
kCFRunLoopBeforeTimers = (1UL << 1),// 即将处理 timer
kCFRunLoopBeforeSources = (1UL << 2),// 即将处理 source
kCFRunLoopBeforeWaiting = (1UL << 5),//即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6),// 刚才休眠中唤醒
kCFRunLoopExit = (1UL << 7),// 即将推出
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
我们runloop 的运行流程为
1. 通知 observer,进入runloop
2. 通知 observer,即将处理 timer 事件
3. 通知 observer,即将处理 source 事件
4. 处理block,runloop 是可以添加 block 的(CFRunLoopPerformBlock(<#CFRunLoopRef rl#>, <#CFTypeRef mode#>, <#^(void)block#>))
5. 处理 source 0
6. 处理 source1事件,如果存在就处理,跳转到第8
7. 通知 observer 即将休眠
8. 通知 observer 结束休眠(timer 事件,source1 事件等)
9. 然后根据前面的执行结果,决定是否再次循环
1. source0 代表触摸事件的处理
我们在 `-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"---");
}`
这个方法里面打个断电,然后控制台输入 lldb 命令,`bt`,来打印下函数的调用栈
bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
* frame #0: 0x000000010b9e5dad blogTest`-[ViewController touchesBegan:withEvent:](self=0x00007fa8b9703c30, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x0000600002cecaa0) at ViewController.m:108:5
frame #1: 0x000000010f7d4863 UIKitCore`forwardTouchMethod + 340
frame #2: 0x000000010f7d46fe UIKitCore`-[UIResponder touchesBegan:withEvent:] + 49
frame #3: 0x000000010f7e38de UIKitCore`-[UIWindow _sendTouchesForEvent:] + 1867
frame #4: 0x000000010f7e54c6 UIKitCore`-[UIWindow sendEvent:] + 4596
frame #5: 0x000000010f7c053b UIKitCore`-[UIApplication sendEvent:] + 356
frame #6: 0x000000010f84171a UIKitCore`__dispatchPreprocessedEventFromEventQueue + 6847
frame #7: 0x000000010f8441e0 UIKitCore`__handleEventQueueInternal + 5980
frame #8: 0x000000010c410471 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #9: 0x000000010c41039c CoreFoundation`__CFRunLoopDoSource0 + 76
frame #10: 0x000000010c40fb74 CoreFoundation`__CFRunLoopDoSources0 + 180
frame #11: 0x000000010c40a87f CoreFoundation`__CFRunLoopRun + 1263
frame #12: 0x000000010c40a066 CoreFoundation`CFRunLoopRunSpecific + 438
frame #13: 0x000000011598dbb0 GraphicsServices`GSEventRunModal + 65
frame #14: 0x000000010f7a7d4d UIKitCore`UIApplicationMain + 1621
frame #15: 0x000000010b9e8f44 blogTest`main(argc=1, argv=0x00007ffee421ad60) at main.m:20:12
frame #16: 0x000000010dd6cc25 libdyld.dylib`start + 1
可以看到中间 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
是 source0 触发的。
2. source1
source1 是基于port 触发的,比如两个线程之间的通讯,线程可能是有 port,两个线程之间的通讯通过这个port通讯
source1 ,系统事件的捕捉,比如点击屏幕的事件,首先是系统事件的捕捉,source1触发,然后传递给source0。
3. timer 事件
我们平时用的 nstimer 或者 perform方法带timer的那个,都属于timeer事件
4. observer 事件
用于监听 runloop 的状态,比如 UI刷新,在我们的runloop即将睡觉之前灰刷新UI,比如我们设置view背景颜色,当我们obser监听到runloop beforeWaiting 之前灰去刷新UI。
验证
我们来监听一下 runloop
// 创建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, SCX_CFRunLoopObserverCallBack, NULL);
// 监听 runloop
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
CFRelease(observer);
// runloop 监听回调
void SCX_CFRunLoopObserverCallBack(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;
}
// 触摸屏幕事件
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSLog(@"---");
}
然后我们来测试一下,然后拉啊哎点击下屏幕,看会发生什么事情
2020-02-19 10:21:56.965997+0800 blogTest[6792:159633] kCFRunLoopAfterWaiting
2020-02-19 10:21:56.966182+0800 blogTest[6792:159633] kCFRunLoopBeforeTimers
2020-02-19 10:21:56.966323+0800 blogTest[6792:159633] kCFRunLoopBeforeSources
2020-02-19 10:22:00.950737+0800 blogTest[6792:159633] ---
2020-02-19 10:22:00.951125+0800 blogTest[6792:159633] kCFRunLoopBeforeTimers
2020-02-19 10:22:00.951263+0800 blogTest[6792:159633] kCFRunLoopBeforeSources
2020-02-19 10:22:00.959414+0800 blogTest[6792:159633] kCFRunLoopBeforeTimers
2020-02-19 10:22:00.959583+0800 blogTest[6792:159633] kCFRunLoopBeforeSources
2020-02-19 10:22:00.959727+0800 blogTest[6792:159633] kCFRunLoopBeforeTimers
2020-02-19 10:22:00.959859+0800 blogTest[6792:159633] kCFRunLoopBeforeSources
2020-02-19 10:22:00.959997+0800 blogTest[6792:159633] kCFRunLoopBeforeWaiting
从打印我们可以看出,在打印---
之前,打印了 kCFRunLoopBeforeSources
,说明开始处理 source 事件,我们的触摸屏幕事件也确实是source事件,
网友评论