RunLoop的用

作者 : 开心源码 本文共15129个字,预计阅读时间需要38分钟 发布时间: 2022-05-12 共61人阅读

RunLoop是什么? 它有什么作使用?
Runloop和多线程又是什么关系?
timer 与 Runloop 有什么关系?用的时候要注意些什么?
平常工作中那些地方使用到了RunLoop?

看到这些问题有没有很头大 很懵逼?不焦急,带着这些问题我们来一步步的揭开RunLoop的神秘面纱…

一. RunLoop简介

RunLoop 顾名思义, 运行循环. 在程序运行过程中循环去做少量事情
下面我们先简单来感受一下它的存在吧

首先来看看假如应使用程序没有RunLoop之后会是什么样子,如下代码:

当执行完第13行代码的时候,应使用程序就会即将退出了.

而假如有了RunLoop,如下图所示(UIApplicationMain函数内部会自动开启一个运行循环),程序并不会立即退出,而是保持运行状态. 由此可见 RunLoop对于我们应使用程序程序来说是至关重要的.


二. RunLoop基本作使用:

  1. 保持程序的持续运行.由于程序一启动的时候就会立即执行 UIApplicationMain函数,而我们知道在该函数内部,系统帮助我们开启了一个主运行循环,所以也就保证了程序不会立即退出
  2. 决定程序在何时应该去解决那些Event
  3. 节省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对象};

其中CFRunLoopRefCFRunLoopModeRef的关系如下图所示:


Runloop总是运行在某种特定的模式(CFRunLoopModeRef)下,
而通过CFRunloopRef对应结构体的定义可以很容易知道每种Runloop都可以包含若干个Mode,每个Mode又包含Source TimerObserver。每次调使用Runloop的主函数__CFRunLoopRun()时必需指定一种Mode,这个Mode称为 _currentMode,当切换Mode时必需退出当前Mode,而后重新进入Runloop以保证不同Mode的Source TimerObserver互不影响。
假如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监测的是kCFRunLoopBeforeWaitingkCFRunLoopExit两种状态.
kCFRunLoopBeforeWaiting(即将进入休眠)时会调使用objc_autoreleasePoolPop()objc_autoreleasePoolPush()方法. 系统会根据情况从最新加入的对象一直往前清除直到遇到POOL_BOUNDARY标志
而在即将退出RunLoop时会调使用objc_autoreleasePoolPop() 方法释放自动释放池内对象。

处理TableView滑动卡慢的小思路

为了防止TableView在滑动的时候造成卡慢的现象我们可以这样设置图片:

参考博客:
深入了解RunLoop
iOS刨根问底-深入了解RunLoop

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » RunLoop的用

发表回复