关于Runloop的理解

一、Runloop简介

概念:

从字面上来看,RunLoop是运行循环的意思,实质上,RunLoop是一种寄生于线程的消息循环机制,让线程能随时处理事件但不退出,保证线程的存活,而不是线程执行完任务后就消亡。

RunLoop实际上是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行Event Loop的逻辑。线程执行了这个函数后,就会一直处于这个函数内部接受消息→等待→处理的循环中,直到达到循环结束条件退出(比如传入 quit 的消息),函数返回。让线程在没有消息处理时休眠以避免资源占用、在有消息到来时立刻被唤醒。

特性:

  1. 每一个线程都有一个对应的RunLoop对象,主线程默认开启RunLoop,这能保证应用程序的执行运行,接收并处理各种事件;子线程默认不开启。

  2. RunLoop有三种启动方式:runrunUntilDate:runMode:beforeDate:
    第一种无条件永远运行RunLoop并且无法停止,线程永远存在。第二种会在时间到后退出RunLoop,同样无法主动停止RunLoop。前两种都是在NSDefaultRunLoopMode模式下运行。第三种可以选定运行模式,并且在时间到后或者触发了非Timer的事件后退出。

  3. RunLoop能正常运行的条件:运行在添加了至少一个事件源(Timer/Source/Observer)mode下,否则会自动退出。经过NSRunLoop封装之后,只可以往mode中添加两类事件:NSPortNSTimer

五个类:

  • CFRunLoopRef:RunLoop对象
  • CFRunLoopModeRef:RunLoop的运行模式
  • CFRunLoopSourceRef:事件产生的地方
  • CFRunLoopTimerRef:定时器(NSTimer)
  • CFRunLoopObserverRef:观察者,回调Runloop状态

iOS 中的RunLoop:

在iOS系统中,提供了两种RunLoop对象:NSRunLoopCFRunLoopRef

  • CFRunLoopRef是在CoreFoundation框架内,它提供了面向过程的纯C函数API

  • NSRunLoop是基于CFRunLoopRef的封装,提供了面向对象的API

两者最主要的区别在于:NSRunLoop是非线程安全的,意味着只能在当前线程中获取线程对应的RunLoop(主线程RunLoop除外),而CFRunLoopRef是线程安全的。

我们不能够手动创建RunLoop,系统为我们提供了获取的方法

[NSRunLoop currentRunLoop]; //获取当前线程的RunLoop
[NSRunLoop mainRunLoop]; //获取主线程的RunLoop
CFRunLoopGetMain(); //获取当前线程的RunLoop
CFRunLoopGetCurrent(); //获取主线程的RunLoop

作用:

  1. 让主线程一直处于收发消息的状态,不会自动结束,保证了程序的运行
  2. 处理App中的各种事件(比如触摸事件、定时器事件等)
  3. 节省CPU资源,提高程序性能

二、Runloop的模式和状态

  1. 一个Runloop对应一条线程 。[1]
  1. 一个Runloop里面可以有多个mode,每一个Mode又可以包含多个mode item
  2. source/timer/observer被统称为mode item,不同的modemode item互不影响。
  3. 同一个时刻,RunLoop只能是在一个mode上面的运行。如果需要切换mode,只能是退出currentMode,切换到指定的mode
image

1、RunLoop的模式

总共是有五种CFRunLoopMode:(前2种常见的及后3种不常见的、系统的)

  • kCFRunLoopDefaultMode:默认模式,主线程是在这个运行模式下运行
  • UITrackingRunLoopMode:界面跟踪mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他mode影响(当一旦滚动屏幕,就会自动切换到这个模式)
  • UIInitializationRunLoopMode:在刚启动App时第进入的第一个mode,启动完成后就不再使用
  • GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到
  • kCFRunLoopCommonModes:伪mode,若干个mode的集合,是同步Source/Timer/Observer到多个mode中的一种解决方案

2、RunLoop的状态

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL << 0), // 即将进入Loop
    kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
    kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒
    kCFRunLoopExit          = (1UL << 7), // 即将退出Loop
};

三、RunLoop的运行逻辑

运行逻辑

总结成一句话就是:RunLoop的运行逻辑就是do-while循环下运用观察者模式(或者说是消息发送),根据7种状态的变化,处理事件输入源和定时器。


四、面试题

1、什么是RunLoop?

  • 从字面上理解,RunLoop是个运行循环。
  • 其实它内部就是do-while循环,在这个循环内部不断的处理各种任务(比如Source\Timer\Observer)
  • 一个线程对应一个RunLoop,主线程的RunLoop默认已经启动,子线程的RunLoop需要手动启动(调用run方法)
    RunLoop只能选择一个mode启动,如果当前mode中没有任何Soure\Timer\Observer,那么就直接退出RunLoop

2、在开发中如何使用RunLoop?什么应用场景?

场景一:解决NSTimer在ScrollView滑动的时候失效的问题

static int count = 0;
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES >block:^(NSTimer * _Nonnull timer) {
   NSLog(@"count == %d",count++);
}];

原因
NSTimerRunLoopmodeNSDefaultRunLoopMode中的,当滑动的时候RunLoop会切换到UITrackingRunLoopMode,所以NSTimer会失效。

解决
Timer添加到NSRunLoopCommonModes模式下面

NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
   NSLog(@"count == %d",count++);
}];
   
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

  1. 线程和runloop是一一对应的关系,内部是一个字典,key为线程,value为runloop

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。