RunLoop的使用
RunLoop是什么? 它有什么作用?
Runloop和多线程又是什么关系?
NSTimer 与 Runloop 有什么关系?使用的时候要注意些什么?
平时工作中那些地方用到了RunLoop?
看到这些问题有没有很头大 很懵逼?不着急,带着这些问题我们来一步步的揭开RunLoop
的神秘面纱...
一. RunLoop简介
RunLoop
顾名思义 运行循环 在程序运行过程中循环去做一些事情
下面我们先简单来感受一下它的存在吧
首先来看看如果应用程序 没有RunLoop
之后会是什么样子,如下代码:
当执行完第13行代码的时候,应用程序就会即将退出了.
而如果 有了RunLoop
,如下图所示(UIApplicationMain
函数内部会自动开启一个运行循环),程序并不会立即退出,而是保持运行状态. 由此可见 RunLoop
对于我们应用程序程序来说是至关重要的.
二. RunLoop基本作用:
- 保持程序的持续运行.因为程序一启动的时候就会立即执行
UIApplicationMain
函数,而我们知道在该函数内部,系统帮助我们开启了一个主运行循环,所以也就保证了程序不会立即退出 - 决定程序在何时应该去处理那些Event
- 节省CPU的资源,提高程序的性能.该做事的时候呢就去处理事情,没有事情做得时候就进入一个自我休眠的状态
三. RunLoop的执行流程
我们通常所说的RunLoop
其实指的是NSRunloop
或者CFRunloopRef
,后者是纯C语言的函数,NSRunLoop
是基于 CFRunLoopRef
的封装,提供了面向对象的 API,但是这些 API 不是线程安全的,所以这里我们主要来分析CFRunloopRef
对象,你可以在这里 https://opensource.apple.com/tarballs/CF/
下载到整个 CoreFoundation
的源码。
当我们在控制器中点击一个UIButton
的时候 他的方法调用栈大概是下图这个样子:
由图中我们可以看到几个关于RunLoop
的几个重要的函数:CFRunLoopRunSpecific
__CFRunLoopRun
等
为了搞明白RunLoop的运行逻辑 下面我们来尝试着分析一下源码:
首先我们来看看这个入口函数 CFRunLoopRun(void)
函数,具体代码如下:
通过该函数我们可以一目了然的看到 RunLoop
内部其实就是一个 do- while
的死循环,通过这个循环一直不断的去处理事情.
而该方法内部,具体的事情是交给了 CFRunLoopRunSpecific
函数去负责执行的,那么我们来看看该函数,为了便于阅读这里我摘录部分代码:
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
// 通知即将进入runloop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知即将退出runloop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
其实最终干活的部分是交给了__CFRunLoopRun
这个函数(如果不想看,可以直接跳过)
int32_t __CFRunLoopRun()
{
do
{
// 通知监听者 准备处理事情
__CFRunLoopDoObservers(kCFRunLoopBeforeTimers);
__CFRunLoopDoObservers(kCFRunLoopBeforeSources);
// 处理非延迟的主线程调用
__CFRunLoopDoBlocks();
// 处理Source0事件
__CFRunLoopDoSource0();
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks();
}
/// 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort();
if (hasMsg) goto handle_msg;
}
/// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
// GCD dispatch main queue
CheckIfExistMessagesInMainDispatchQueue();
//告诉监听者,我要准备睡觉了
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
// 没有事情, 阻塞等待,睡眠,
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
// 通知监听者,我要处理事情
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
// 等待内核mach_msg事件
mach_port_t wakeUpPort = SleepAndWaitForWakingUpPorts();
// 从等待中醒来
__CFRunLoopDoObservers(kCFRunLoopAfterWaiting);
// 处理因timer的唤醒
if (wakeUpPort == timerPort)
__CFRunLoopDoTimers();
// 处理异步方法唤醒,如dispatch_async
else if (wakeUpPort == mainDispatchQueuePort)
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()
// 处理Source1
else
__CFRunLoopDoSource1();
// 再次确保是否有同步的方法需要调用
__CFRunLoopDoBlocks();
} while (!stop && !timeout);
其实我们不用逐行的去阅读它的源代码,只需要知道RunLoop
的内部其实就是一个do-while
循环就可以了,有消息的时候 RunLoop
就会去处理消息,否则就是出于休眠状态. 当然这里的休眠不同于我们自己写的死循环(while(1);
),它在休眠时几乎不会占用系统资源,当然这是由操作系统内核去负责实现的.
具体的执行流程如下图所示:参考资料Kenshin Cui's Blog-深入理解RunLoop
四. RunLoop和多线程的关系
苹果并没有为我们提供一个可以直接创建Runloop
的接口,但是我们可以通过CFRunLoopGetMain()
和CFRunLoopGetCurrent()
两个方法来获取RunLoop
对象,这两个方法内部的逻辑大概如下:
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
// 如果__CFRunLoops不存在,则创建一个CFRunLoopRef对象
if (!__CFRunLoops) {
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 将数据保存到字典中,key是线程,value是runloop
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
// 如果__CFRunLoops存在,则根据当前线程 去获取runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 如果runloop 为空
if (!loop) {
// 创建了一个runloop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
// 再次获取一次
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
// 关联起来
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
//当线程销毁时,顺便也销毁其对应的 RunLoop。
_CFSetTSD(__CFTSDKeyRunLoopCntr, PTHREAD_DESTRUCTOR_ITERATIONS-1, __CFFinalizeRunLoop);
}
}
return loop;
}
从上面的代码我们可以看出,每条线程都有唯一的一个与之对应的RunLoop对象,其关系是保存在一个全局的 Dictionary
里,线程作为Key而RunLoop
作为Value。线程刚创建时并没有对应的 RunLoop,如果你不主动获取,那它一直都不会有。
RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。
主线程的RunLoop已经自动获取(创建),而子线程默认没有开启RunLoop
启动RunLoop的几个方法
//CFRunLoopRun;该方法并不会主动退出,除非调用CFRunLoopStop();如果想要永远不会退出RunLoop才会使用此方法,否则可以使用runUntilDate。
- (void)run;
//CFRunLoopRunInMode(mode,limiteDate,true);执行完就退出;通常用于手动控制RunLoop
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
//CFRunLoopRunInMode(kCFRunLoopDefaultMode,limiteDate,false);执行完并不会退出,继续下一次RunLoop直到timeout。
- (void)runUntilDate:(NSDate *)limitDate;
四.RunLoop的相关类
在 CoreFoundation
里面关于RunLoop
有5个类:
1. CFRunLoopRef
2. CFRunLoopModeRef
3. CFRunLoopSourceRef
4. CFRunLoopTimerRef
5. CFRunLoopObserverRef
CFRunLoopRef 和 CFRunLoopModeRef的结构大致如下:
struct __CFRunLoopMode {
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
};
struct __CFRunLoop {
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;//RunLoop当前运行模式
CFMutableSetRef _modes;//RunLoop运行模式,里面存放着CFRunLoopModeRef对象
};
其中CFRunLoopRef
和CFRunLoopModeRef
的关系如下图所示:
RunLoop
总是运行在某种特定的模式(CFRunLoopModeRef
)下,而通过
CFRunLoopRef
对应结构体的定义可以很容易知道每种Runloop都可以包含若干个Mode,每个Mode又包含Source
Timer
和 Observer
。每次调用Runloop的主函数__CFRunLoopRun()
时必须指定一种Mode,这个Mode称为 _currentMode
,当切换Mode时必须退出当前Mode,然后重新进入Runloop以保证不同Mode的Source
Timer
和 Observer
互不影响。如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
常见的2种Mode
kCFRunLoopDefaultMode:: App的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
kCFRunLoopCommonModes(NSRunLoopCommonModes):其实这个并不是某种具体的Mode,而是一种模式组合,并不是说Runloop会运行在该模式下,而是相当于向_commonModes
中注册了 NSDefaultRunLoopMode
和 UITrackingRunLoopMode
这两种模式。
应用场景
当我们开启一个定时器,假如3秒钟就要执行一段代码,正常情况下是没有任何问题的,但是当我们手动滑动UIScrollView,NSTimer就会暂停,当我们停止滑动以后,NSTimer又会重新恢复的情况,我们通过一段代码来看一下
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//与下面两句等同,系统默认帮助我们将timer加入到了RunLoop对象中,并且设置模式为NSDefaultRunLoopMode
//[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(Test) userInfo:nil repeats:YES];
NSTimer *timer = [NSTimer timerWithTimeInterval:3.0 target:self selector:@selector(Test) userInfo:nil repeats:YES ];
//不添加到RunLoop中的NSTimer是无法正常工作的
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
-(void)Test
{
NSLog(@"Hello World");
}
运行程序后点击屏幕,打印结果如下图所示:
通过打印结果我们可以看到time的前三次执行是没有问题的,但是红框中的部分间隔了11秒之后才再次执行,这是因为在这期间我滑动了 UIScrollView 为什么会这样呢 ?
因为在滑动UIScrollView的时候,RunLoop就切换到
UITrackingRunLoopMode
模式,因此timer失效,当停止滑动,RunLoop又会切换回NSDefaultRunLoopMode
模式,因此timer又会重新启动了
(如果对NSTimer
的其他使用问题感兴趣 可以看看我的这篇文章:定时器使用的相关问题)
解决方案:
我们只需要将timer添加到RunLoop的时候指定模式为kCFRunLoopCommonModes
模式即可,这样不管是在Default模式下还是Tracking模式下timer都可以执行
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
CFRunLoopSourceRef
Source有两个版本:Source0
和Source1
。
Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal
,将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp
来唤醒 RunLoop,让其处理这个事件。
Source1 包含了一个 mach_port
和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。
CFRunLoopTimerRef
是基于时间的触发器,它包含一个时间长度和一个回调方法 当加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行回调方法。
CFRunLoopObserverRef
CFRunLoopObserverRef相当于消息循环中的一个监听器,随时通知外部当前RunLoop
的运行状态
其中RunLoop
的几种运行状态如下:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 进入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将开始Timer处理
kCFRunLoopBeforeSources = (1UL << 2), // 即将开始Source处理
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), //从休眠状态唤醒
kCFRunLoopExit = (1UL << 7), //退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
我们可以通过CFRunLoopObserverCreateWithHandler
函数来监听RunLoop
的运行状态
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"RunLoop进入");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"RunLoop要处理Timers了");
break;
case kCFRunLoopBeforeSources:
NSLog(@"RunLoop要处理Sources了");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"RunLoop要休息了");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"RunLoop醒来了");
break;
case kCFRunLoopExit:
NSLog(@"RunLoop退出了");
break;
default:
break;
}
});
// 给RunLoop添加监听者
/*
第一个参数 CFRunLoopRef rl:要监听哪个RunLoop,这里监听的是主线程的RunLoop
第二个参数 CFRunLoopObserverRef observer 监听者
第三个参数 CFStringRef mode 要监听RunLoop在哪种运行模式下的状态
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
CFRelease(observer);
}
几道练习题目
1. 看看下面这段代码, 写出打印结果
- (void)test{
NSLog(@"任务B");
}
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任务A");
[self performSelector:@selector(test) withObject:nil afterDelay:1.0];
NSLog(@"任务C");
});
}
知道结果了么?
我们来看看打印结果:
为什么只输出了任务A和任务C而没有任务B呢?其实这里涉及到了
RunLoop的知识
,因为performSelector:withObject:afterDelay:
的本质是向RunLoop
中添加定时器,而子线程中默认是没有开启RunLoop
的,所以这里我们需要稍微改动下代码,如下;
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"任务A");
[self performSelector:@selector(hahha) withObject:nil afterDelay:1.0];
NSLog(@"任务C");
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
}
关于RunLoop
有兴趣的朋友可以看看我的这篇文章: RunLoop的使用
2. 面试题目:写出打印结果
- (void)test{
NSLog(@"任务B");
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"任务A");
}];
[thread start];
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
执行结果:
[73860:11959832] 任务A
[73860:11959410] *** Terminating app due to uncaught exception 'NSDestinationInvalidException', reason: '*** -[ViewController performSelector:onThread:withObject:waitUntilDone:modes:]: target thread exited while waiting for the perform'
因为我们在执行完[thread start];
的时候执行任务A,此时线程就被销毁了,如果我们要在thread
线程中执行test
方法需要保住该线程的命,即线程保活
,代码需要修改如下:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"任务A");
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}];
[thread start];
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
关于RunLoop的应用
常驻线程
在实际开发过程中,我们会将花费时间比较长的操作放在子线程中来处理,当当子线程中的任务执行完毕后,子线程就会被销毁掉.
验证该结论:
首先我们创建一个继承自NSThread
的子类对象GSThread
,重写dealloc
方法
@implementation GSThread
-(void)dealloc{
NSLog(@"%s",__func__);
}
@end
在ViewController
中,当点击屏幕的时候开启一个子线程,如下所示:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
GSThread *thread = [[GSThread alloc] initWithTarget:self selector:@selector(Test) object:nil];
[thread start];
}
-(void)Test{
NSLog(@"%s--%@",__func__,[NSThread currentThread]);
}
打印结果:
由打印结果我们可以看到当子线程中的任务执行完毕后,线程就被立刻销毁了。如果程序中,需要经常在子线程中执行任务,频繁的创建和销毁线程,就会造成资源的浪费。这时候我们就可以使用RunLoop
来让该线程长时间存活而不被销毁。
我们将上面的代码修改一下,应用程序启动以后创建一个子线程,当子线程启动后,启动runloop,点击视图在该子线程中执行任务.
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[GSThread alloc] initWithTarget:self selector:@selector(runThread) object:nil];
[self.thread start];
}
//该方法的目的是为了保住线程的命,也叫线程报活或者是常驻线程
- (void)runThread{
NSLog(@"%s--%@",__func__,[NSThread currentThread]);
[[NSRunLoop currentRunLoop] addPort:[NSPort new] forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
NSLog(@"end.....");
}
//点击屏幕的时候在创建的子线程中执行任务
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self performSelector:@selector(Test) onThread:self.thread withObject:nil waitUntilDone:YES];
}
//子线程需要执行的任务
-(void)Test{
NSLog(@"%s--%@",__func__,[NSThread currentThread]);
}
@end
输出结果:
可以看到当执行完Test方法后该子线程并没有销毁,这也就实现了我们的目的.但是什么时候销毁该线程呢?
为了使用方便 我们将该常驻线程封装到了一个实体类中,具体代码如下:
typedef void (^GSPermenantThreadTask)(void);
@interface GSPermenantThread : NSObject
/** 在当前子线程执行一个任务 */
- (void)executeTask:(GSPermenantThreadTask)task;
/** 结束线程 */
- (void)stop;
@end
#import "GSPermenantThread.h"
/** GSThread **/
@interface GSThread : NSThread
@end
@implementation GSThread
- (void)dealloc
{
NSLog(@"%s", __func__);
}
@end
/** GSPermenantThread **/
@interface GSPermenantThread()
@property (strong, nonatomic) GSThread *innerThread;
@end
@implementation GSPermenantThread
#pragma mark - public methods
- (instancetype)init
{
if (self = [super init]) {
self.innerThread = [[GSThread alloc] initWithBlock:^{
NSLog(@"begin----");
// 创建上下文(要初始化一下结构体)
CFRunLoopSourceContext context = {0};
// 创建source
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
// 往Runloop中添加source
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
// 销毁source
CFRelease(source);
// 启动RunLoop对象
// 参数三:执行完当前任务后 RunLoop对象是否退出
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
NSLog(@"end----");
}];
[self.innerThread start];
}
return self;
}
- (void)executeTask:(GSPermenantThreadTask)task
{
if (!self.innerThread || !task) return;
[self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}
- (void)stop
{
if (!self.innerThread) return;
[self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self stop];
}
#pragma mark - private methods
- (void)__stop
{
CFRunLoopStop(CFRunLoopGetCurrent());
self.innerThread = nil;
}
- (void)__executeTask:(GSPermenantThreadTask)task
{
task();
}
@end
使用的时候我们只需要创建该实体类,然后传入我们要进行的操作即可:
#import "ViewController.h"
#import "GSPermenantThread.h"
@interface ViewController ()
@property (strong, nonatomic) GSPermenantThread *thread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[GSPermenantThread alloc] init];
}
//点击屏幕的时候在该子线程中执行任务
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self.thread executeTask:^{
NSLog(@"执行任务 - %@", [NSThread currentThread]);
}];
}
//手动销毁该子线程
- (IBAction)stop {
[self.thread stop];
}
- (void)dealloc
{
NSLog(@"%s", __func__);
}
@end
autorelease
关于autorelease
的使用以及内部分析可以参考我的这篇文章:@autoreleasepool和autorelease的使用
在应用程序刚启动的时候我们打印当前的RunLoop
对象
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"%@",[NSRunLoop currentRunLoop]);
}
部分结果如下:
由上图我们可以看到iOS应用启动后RunLoop
会注册两个Observer来管理和维护AutoreleasePool
上面我们讲过 RunLoop
的几种状态,这里在列举一下
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 0 进入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 2 即将开始Timer处理
kCFRunLoopBeforeSources = (1UL << 2), // 4 即将开始Source处理
kCFRunLoopBeforeWaiting = (1UL << 5), // 32 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 64 从休眠状态唤醒
kCFRunLoopExit = (1UL << 7), // 128 退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
可以看到 activities = 0x1
监测的是kCFRunLoopEntry
也就是进入RunLoop的状态,此时它会回调objc_autoreleasePoolPush()
方法向当前的AutoreleasePoolPage
增加一个POOL_BOUNDARY
标志创建自动释放池。
而activities = 0xa0
监测的是kCFRunLoopBeforeWaiting
和kCFRunLoopExit
两种状态.
在kCFRunLoopBeforeWaiting
(即将进入休眠)时会调用objc_autoreleasePoolPop()
和objc_autoreleasePoolPush()
方法. 系统会根据情况从最新加入的对象一直往前清理直到遇到POOL_BOUNDARY
标志
而在即将退出RunLoop时会调用objc_autoreleasePoolPop()
方法释放自动释放池内对象。
解决TableView滑动卡顿的小思路
当Cell中有多张图片的时候 上下滑动可能会造成TableView的卡顿 为了解决这个问题我们可以将设置图片的代码放到NSDefaultRunLoopMode
模式下 如下图:
在以后的工作中 如果遇到关于RunLoop
的问题我会统统记录在这里 欢迎大家一起讨论学习☺
本文会持续更新....