RunLoop

作者: xzZZzx | 来源:发表于2017-09-19 18:21 被阅读19次

    在我们平时的开发过程中会涉及到RunLoop的开发其实是非常少的,但是RunLoop其实是保证App能够正常运行的一个非常关键的东西。

    什么是RunLoop?
    我们现在的手机系统都是一个事件驱动模式的系统,当用户触发了某个事件,系统会快速的进行响应处理。一般的线程都是从开始直接走到结束的一个直线型的过程,结束后线程就被释放了。而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来进行不同事件的调用时机控制

    相关文章

      网友评论

          本文标题:RunLoop

          本文链接:https://www.haomeiwen.com/subject/ojkmsxtx.html