JHHK

欢迎来到我的个人网站
行者常至 为者常成

RunLoop(1)

目录

介绍

一、介绍

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的运行逻辑

img

一、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函数执行完毕之后将直接返回,也就没有程序持续运行一说了。


行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.