RunLoop是什么? 它有什么作使用?
Runloop和多线程又是什么关系?
timer 与 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
的源码。
下面我们来尝试着分析一下源码:
首先我们来看看这个入口函数 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的几个方法
//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和多线程的关系
苹果并没有为我们提供一个可以直接创立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的相关类
在 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这两种模式。当然你也可以通过调使用CFRunLoopAddCommonMode()方法将自己设置Mode放到 kCFRunLoopCommonModes组合)。
应使用场景
当我们开启一个定时器,如果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又会重新启动了
处理方案:
我们只要要将timer增加到RunLoop的时候指定模式为kCFRunLoopCommonModes
模式就可,这样不论是在Default模式下还是Tracking模式下timer都可以执行
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
CFRunLoopSourceRef
Source有两个版本:Source0 和 Source1。
Source0 只包含了一个回调(函数指针),它并不能主动触发事件。用时,你需要先调使用 CFRunLoopSourceSignal(source),将这个 Source 标记为待解决,而后手动调使用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其解决这个事件。
Source1 包含了一个 mach_port 和一个回调(函数指针),被使用于通过内核和其余线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。
CFRunLoopTimerRef
是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混使用。其包含一个时间长度和一个回调(函数指针)。当其加入到 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滑动卡慢的小思路
为了防止TableView在滑动的时候造成卡慢的现象我们可以这样设置图片:
参考博客:
深入了解RunLoop
iOS刨根问底-深入了解RunLoop