iOS 开发 iOS Developer

RunLoop(基础部分)

2016-09-16  本文已影响267人  雷晏

在很多地方,都能看到这个概念,却不知道到底是个什么玩意,网上写的也很抽象,理解起来很吃力,官方文档官方文档也只是泛泛而谈。

那么,什么是RunLoop?

苹果在取名方面我觉得非常不错,runloop就像字面上的意思一样运行循环,俗称“兜圈圈”。也就是死循环,在这个循环中,会去处理一系列的事件。假如说这个循环停止了,那么程序就已经停止了。

死循环

在创建一个工程,都会有个main函数,在这个函数里就是去运行程序的时候创建了个主线程的RunLoop,也是保持程序持续运行的关键。

如果改成return 0;那么程序将不会持续运行,刚运行就停止了。也不会去处理app的各种事件,这也是为什么要去使用RunLoop了,同样,在其他操作系统上也有这个概念。

请问这个打印会正常打印出来么?可以先思考下为什么。

答案是不会打印的,因为在上一句,就成死循环,也就是弄了个runloop,一直卡在上一句代码,也就是保持了程序的持续运行,如果说能打印,那么return也能返回值,那么程序就没法持续保持运行,一旦返回值那么意味着结束。


RunLoop有什么用呢?

1.保持程序的持续运行

2.处理App的各种事件(比如触摸事件、定时器事件、Selector事件)

3.节省CPU资源,提高程序性能。(该做事的时候做事,该休息的时候休息)


RunLoop对象

在iOS中有2套API去访问和使用RunLoop

Foundation框架

NSRunLoop

Core Foundation框架

CFRunLoopRef

NSRunLoop和CFRunLoopRef都代表RunLoop对象,NSRunLoop是基于CFRunLoopRef的一层OC封装,为此要理解RunLoop内部结构,要多研究CFRunLoopRef

NSRrunLoop缺点:效率低(因为是上层封装),封装的不算好,可提供的方法很少。

CFRunLoopRef是开源的,开源地址CFRunLoopRef开源地址


RunLoop和线程

1.每条线程都有唯一的一个与之对应的RunLoop对象

2.主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建(默认关闭的)

3.RunLoop在第一次获取时创建,在线程结束时销毁。


如何获取RunLoop对象

Foundation框架

[NSRunLoop currentRunLoop];//获取当前线程的RunLoop对象

[NSRunLoop mainRunLoop];//获取主线程的RunLoop对象

Core Foundation

CFRunLoopGetCurrent();//获取当前线程的RunLoop对象

CFRunLoopGetMain();//获得主线程的RunLoop对象


RunLoop相关类

类层次结构图

Core Foundation中关于RunLoop有5个类

CFRunLoopRef

CFRunLoopModeRef

CFRunLoopSourceRef

CFRunLoopTimerRef  

CFRunLoopObserverRef

RunLoop中有多个Mode,可以在多个Mode中来回切换,Mode是RunLoop重要的组成部分,没有Mode,RunLoop就跑不起来.

在一个Mode里,不是必须Source,Timer,Observer三个必须有值,有一个有值,RunLoop也是可以跑起来的。


CFRunLoopModeRef

1.CFRunLoopModeRef代表RunLoop的运行模式

2.一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer

3.每次RunLoop启动时,只能指定其中一个Mode,这个Mode又叫做CurrentMode

4.如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入Loop。这样做主要是为了分隔不同组的Source/Timer/Observer,让其互不影响。

系统默认注册了5个Mode:

kCFRunLoopDefaultMode:app默认Mode,通常主线程是在这个Mode下运行

UITrackingRunLoopMode:界面跟踪Mode,用于Scrollview追踪触摸滑动,保证界面滑动不受其他Mode影响

UIInitializationRunLoopMode:在刚启动App时进入的第一个Mode,启动完成后就不再使用

GSEventReceiveRunLoopMode:接受系统时间的内部Mode,通常用不到

kCFRunLoopCommonModes:这是一个占位用的Mode,不是一种真正的Mode,即可能kCFRunLoopDefaultMode,也可能是UITrackingRunLoopMode

例子:

当不去滑动TextView时,当前RunLoopMode是NSDefaultMode,所以定时器会正常运作;当去滑动TextView时,当前RunLoopMode切换到UITrackingRunLoopMode,定时器又要在NSDefaultMode模式下才能运行,所以定时器失效;当不再滑动,又切换到NSDefaultMode,定时器又可以正常运作。

我们知道了是因为RunLoop模式导致了,所以,将定时器在的模式更改就行了。

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];


CFRunLoopSourceRef

Mode的一个组成部分,CFRunLoopSourceRef被称为事件源(输入源)。RunLoopSource包括2种source:分别为Source0Source1

Source0:非基于port的

只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用。(通俗点说:1.自定义的事件,自己写的事件;2.PerformSelecter事件)

Source1:基于port的

包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程(简单地说:系统做的事件)

(补充:port就是端口内核的意思,也可以理解为cpu)

上图是我在storyboard拖的button事件方法,通过设置断点,可以看到线程栈的情况,很清楚的看到Source0,也可以说明,事件源是由自定义写的。


CFRunLoopTimerRef

基于时间的触发器(说白了,基本就是NSTimer)


CFRunLoopObserverRef

CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变

可以监听的时间点有下面几个

/*Run Loop Observer Activities*/

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity){

       kCFRunLoopEntry = (1UL << 0),        //即将进入RunLoop

       kCFRunLoopBeforeTimers = (1UL << 1),  //即将处理Timer

       kCFRunLoopBeforeSources = (1UL << 2), //即将处理Source

       kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠

       kCFRunLoopAfterWaiting = (1UL << 8),  //刚从休眠中唤醒

       kCFRunLoopExit = (1UL << 7),         //即将退出RunLoop

       kCFRunLoopAllActivities = 0x0FFFFFFFU   //监听RunLoop所有情况

}

如何去创建一个ObserverRef

列1:我们来看看应用从启动到界面显示,runloop状态是如何变化的。

可以看到,从最开始的1(即将进入RunLoop)到最后的32(即将进入休眠)一个过程。

列1:我们来监听下一个按钮点击后,RunLoop状态的变化

可以看到,从最开始的64(刚从休眠唤醒)到最后的32(即将进入休眠)。

为此,我们可以利用runloop的状态改变来处理一些不好处理的事件,具体需要具体情况而定,这样runloop就可以很好的结合起来使用了。


知道了RunLoop逻辑处理和RunLoop结构后,我们来看看官方提供的RunLoop处理逻辑图就很好理解了。

左图的就是RunLoop,Runloop是跟线程一一绑定的,所以是个Thread。然后Runloop一直跑圈,在跑圈的过程中,右图会有Sources传入进行事件操作,如Timer,基于Port事件,自定义事件,performSelector事件。

RunLoop处理逻辑过程


总结:

    1.主线程的RunLoop默认开启

    2.子线程的RunLoop默认关闭,需要手动开启

    3.RunLoop开启的时候要包含Mode,必须要某个确定的模式下运行,同时在一个模式下运行

    4.observer可以监听RunLoop所有状态事件,可以做到一些别人做不到的事。


上一篇 下一篇

猜你喜欢

热点阅读