乐趣区

iOS-NSRunLoop-介绍

来自:https://www.jianshu.com/p/d71…

是什么?

RunLoop 其实是 iOS 中的一种消息机制的处理模式。字面的意识就是跑圈,那就是循环了呗。对,就是循环!

理解:

学过 C 语言的同学都知道,每个程序从开始运行到完成需要的计算后打印台打印出你需要的信息后就结束了任务。
那么对于我们的手机来说,任何应用在前台他都是在一直处于运行状态的,随时等待你的命令,对吧!那为什么他在做完你一次的命令任务后退出呢?
这个问题的核心就是 RunLoop。
因为 RunLoop 一直在跑圈啊,一直在循环啊,一直在运行啊,你没有杀死进程啊,So… 一直运行着等待你的命令。

作用:

  1. 保持程序的持续运行
  2. 处理 App 中的各种事件(手势、定时器、Selector 等)
  3. 节省 CPU 资源、提高程序性能:该做任务的时候做任务,没事干的时候休息。

理解重点:

RunLoop 和线程是一对一的关系。比如我们创建项目的时候,刚创建项目的时候就会有个主线程,而这个时候就已经创建了 RunLoop 了,所以说在线程创建之初就创建了 RunLoop(只限于主线程,而子线程需要我们去主动获取),因此线程存在于线程的整个生命周期。
然而好多人问子线程怎么销毁 RunLoop?

  1. 首先说主线程的 RunLoop 不能销毁啊,你销毁了程序就退出了。
  2. 我们现在基本所有应用都用是 ARC,我们不需要对内存进行费时的管理,系统会在一个子线程完成的时候销毁掉这个子线程,因此子线程的 RunLoop 也就跟着自动销毁了。

RunLoop 对象
在我们开发 iOS 中有 2 套 API 来访问 RunLoop

  1. Foundation
    NSRunLoop
  2. Core Foundation
    CFRunLoopRef

首先 NSRunLoop 和 CFRunLoopRef 都是 RunLoop 的对象,前者是 Oc,后者是 C。前者是苹果公司对后者的封装,在正常的开发中 NSRunLoop 已经够我们使用了,想要了解更多,使用更多,研究更多就去看后者,他是开源的,不过也很难看懂。其实 RunLoop 底层 C 都是用一个结构体完成的,里面有各种指针、指针函数、bool 值、变量等等等。。。。

RunLoop 相关类

RunLoop 结构图.png

Core Foundation 中关于 RunLoop 的 5 个类

  1. CFRunLoopRef
  2. CFRunLoopModeRef
  3. CFRunLoopSourceRef
  4. CFRunLoopTimerRef
  5. CFRunLoopObserverRef

这五个类按照这个结构图一一对应。
CFRunLoopRef 代表 RunLoop 的实体类,一个 RunLoop 中包含若干个 Mode,而每个 mode 又包含若干个 Source/Timer/Observer。
重点:

  1. 每次运行 RunLoop 都必须指定其中一个 mode,如果没有 mode,RunLoop 无法运行,而这个 mode 被称为当前 mode。
  2. 如果要切换 mode,只能退出当前 RunLoop,然后再重新指定个 mode 进入。
  3. 为什么要像 2 这样做呢?不是很麻烦吗?苹果这样做是为了区分不同组的 Source/Timer/Observer。

系统默认的 5 个 mode

  1. NSDefaultRunLoopMode
    这个 mode 一般是主线程 RunLoop 的默认 mode。创建线程之初 RunLoop 是以这种 mode 运行的。
  2. UITrackingRunLoopMode
    这个 mode 是保证滑动 ScrollView 滑动不受影响,比如滑动 tableView 的时候主线程就切到这个 mode 上了。
  3. UITInitializationRunLoopMode
    在刚启动 App 的时候第一次进入的 mode,启动后就不进入此 mode 了,本人理解是为了防止 App 进入时选择了其他 mode 而运行错乱。
  4. GSEventReceiveRunLoopMode
    接受系统内部的 mode,通常不用。
  5. NSRunLoopCommonModes
    这个 mode 是包换 1 和 2 的,因此解有个一个问题。定时器触发的时候滑动 TableView 定时器会停止。这是因为默认是在第一个 mode 上运行,在滑动的时候 RunLoop 切换到了第二个 mode,所以第一个 mode 上的任务就被搁置了。
    So,解决这个问题就直接把 Timer 加入到第五个 mode 中就完美解决了,滑动的时候也不影响定时器的触发。

事件源(输入源)

Source 是事件源也是输入源,比如点击 Button 按钮、滑动 TableView 都是一种事件源,告诉 RunLoop 需要去做什么!
CFRunLoopSourceRef 分两种:

  1. Source0:非基于 Port 端口,自定义的方法函数、SelectorPerform。简单理解就是你写的就是。
  2. Source1:基于 Port 端口,系统提供默认的方法函数,比如 UIApplicationMain。

定时器

这个就不说了,在上面第五个 mode 已经说了。

CFRunLoopObserverRef

CFRunLoopObserverRef 是观察者,能够监听 RunLoop 所有的状态改变。
可以监听的时间点有如下几种:

RunLoopObserver 监听的时间点.png

从上往下是:
即将进入 RunLoop
即将处理 Timer
即将处理 Source
即将进入休眠
即将从休眠中被唤醒
即将退出 RunLoop
所有状态
这是个枚举,枚举值是 2 的 0 次方,1 次方,2 次方 ……

比如在点击按钮的这个操作过程中,RunLoop 会把这些状态全部按照任务的顺序走一遍。或者你在滑动,轻扫等操作,RunLoop 都会接收到并走这些状态,然后执行任务。
因此有这些状态的判断就可以在好多 Oc 做不到的地方搞些事情,但是 NSRunLoop 里面没有提供 Observer 的这些状态,所以需要用 CoreFoundation 框架来写

NSRunLoop 结构就是这样的!目前介绍完了。

应用

NSRunLoop 的应用:

  1. 延迟显示 ImageView 用 RunLoop。(performSelector: withObject: afterDelay: inModes:)这个方法
  2. NSTimer。需要加入 mode
  3. 常驻线程,就是一直让线程活着。其实就是在创建线程的时候,打开当前 RunLoop,然后加入 mode,然后 Run。这个 AFNetWorking 中也用到了 RunLoop(其实是写了个单例创建了一个线程,然后打开 RunLoop,加入了 mode 的 SourcePort)
  4. 把 3 加入 @autoreleasepool 中,在 runloop 睡眠的时候释放。
退出移动版