iOS点点滴滴iOS DeveloperiOS开发

RunLoop的使用

2017-02-22  本文已影响488人  Mark_Guan

RunLoop是什么? 它有什么作用?
Runloop和多线程又是什么关系?
NSTimer 与 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 的源码。

当我们在控制器中点击一个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对象
};

其中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中注册了 NSDefaultRunLoopModeUITrackingRunLoopMode这两种模式。

应用场景
当我们开启一个定时器,假如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有两个版本:Source0Source1

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监测的是kCFRunLoopBeforeWaitingkCFRunLoopExit两种状态.
kCFRunLoopBeforeWaiting(即将进入休眠)时会调用objc_autoreleasePoolPop()objc_autoreleasePoolPush()方法. 系统会根据情况从最新加入的对象一直往前清理直到遇到POOL_BOUNDARY标志
而在即将退出RunLoop时会调用objc_autoreleasePoolPop() 方法释放自动释放池内对象。

解决TableView滑动卡顿的小思路

当Cell中有多张图片的时候 上下滑动可能会造成TableView的卡顿 为了解决这个问题我们可以将设置图片的代码放到NSDefaultRunLoopMode模式下 如下图:

在以后的工作中 如果遇到关于RunLoop的问题我会统统记录在这里 欢迎大家一起讨论学习☺

本文会持续更新....

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

上一篇下一篇

猜你喜欢

热点阅读