RunLoop的使用

作者: Mark_Guan | 来源:发表于2017-02-22 14:30 被阅读488次

    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

    相关文章

      网友评论

      本文标题:RunLoop的使用

      本文链接:https://www.haomeiwen.com/subject/xfhmwttx.html