在我们平时的开发过程中会涉及到RunLoop的开发其实是非常少的,但是RunLoop其实是保证App能够正常运行的一个非常关键的东西。
什么是RunLoop?
我们现在的手机系统都是一个事件驱动模式的系统,当用户触发了某个事件,系统会快速的进行响应处理。一般的线程都是从开始直接走到结束的一个直线型的过程,结束后线程就被释放了。而RunLoop的存在就是为了能保持线程的持续存在,当有需要的时候进行激活处理事件,不需要的时候进行挂起休眠等待下一次的事件激活。
RunLoop的作用
1、使程序一直保持运行并能在用户有输入操作的时候进行处理
2、决定程序在任何应该处理哪些事情
3、调用解耦,消息队列
4、节省CPU时间(在没有需要处理的消息的时候进行挂起休眠)
这里对2、3进行解释一下,当用户进行操作APP的时候肯定不是仅仅触发某一个事件的,必定是会触发一系列的事件。这时候RunLoop会有个管理的消息队列来做管理,先处理哪个再处理哪个。这时候用户的消息触发与程序的处理其实就解耦分开了,用户只需要做自己的操作,剩下的都交给程序自己去做管理处理。
RunLoop的机制
在我们运行的APP中打上断点并进行触发,看看调用栈
我现在是断在了一个button的点击触发事件那里。我们看看调用堆栈里面的调用顺序。
最下面的start,main是整个程序的起调,然后到了UIKit里面的Main函数,GSEventRunModel这个是物理内核对用户的点击各种触发事件的处理,然后就调到了RunLoop这里,获取了点击事件然后进行事件消息处理分发,到最上的就是我们熟悉的各种层层函数的调用。
我们这边看到箭头指的这个一长串东西,其实是RunLoop的六种起调状态,我们的绝大部分都是从这六种状态进行调起的,但也并不是绝对,如果自己创建一个线程去调用方法,是有可能不从这六个状态进行调起方法的
image.png这里面的CAllING_OUT其实就可以看出来作用是进行调出的,主要看的是标红的地方
这边解释一下最下面两个Source0和Source1
Source0:处理APP内部事件,APP自己负责管理(触发)例如UIEvent
Source1:由RunLoop和内核管理,Mach port驱动
Mach port
轻量级的进程通讯,有点类似于网络通讯端口,比方说手机的定位、网络数据下载,都是手机通过调接口传入到APP内再调到相应的函数
当Xcode点击暂停按钮,APP就会处于一个挂起的trap状态
当另一个线程(或者另一个进程中的某个线程)向内核发消息,trap状态就会被唤醒,进行事件处理
RunLoop的结构
结构这张是从其他地方扒过来的图
RunLoop与线程的关系
在我的理解中,RunLoop是依托于线程的,但是并不是所有线程都会有RunLoop的。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。
CFRunLoopTimer
NSTimer是对这个的一个上层封装
除了GCD的timer其他的timer都是基于RunLoop来实现的
CFRunLoopSource
类似于一个协议Protocol,当符合条件则触发
在RunLoop里面自己定义了两个,一个是Source0一个是Source1
听说还可以自己定义,这里没有仔细研究
CFRunLoopObserver
枚举向外部报告现在的状态
这个与Autorelease Pool的关系
Autorelease Pool
在App启动后, 系统会在主线程里先注册两个Observer, 回调都是_wrapRunLoopWithAutoreleasePoolHandler()
Observer1: 监视即将进入RunLoop, 在这个回调内会去调用_objc_autoreleasePoolPush()来创建一个自动释放池, 它的order是-2147483647, 最高优先级, 酱紫就可以保证创建自动释放池是在其他回调之前
Observer2: 监听了两件事
第一: 在即将进入休眠(BeforeWaiting)的时候调用_objc_autoreleasePoolPop()和_objc_autoreleasePoolPush()
第二: 在即将退出RunLoop(Exit)的时候调用_objc_autoreleasePoolPop()来释放自动释放池
注意: 这个Observer2的order是2147483647, 优先级最低, 酱紫就可以保证在处理完所有事情之后再去释放这个自动释放池
我们在主线程中执行的代码, 一般都是写在事件回调, Timer回调内, 酱紫回调就会被RunLoop所创建的自动释放池(Autorelease Pool)里循环着, 我们不用去担心内存泄漏什么之类的, 也不需要去显示的去创建Pool
Mode
一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
RunLoop与GCD
RunLoop和GCD其实是协调的关系,当GCD触发在主线程dispatch_get_main_queue()做事情的时候,RunLoop做的是帮GCD进行调起
实践
1、保持线程的长久存活
2、通过切换mode来进行不同事件的调用时机控制
网友评论