iOS 开发之RunLoop

作者: DB001 | 来源:发表于2020-05-16 08:05 被阅读0次

    RunLoop

    从字面意思看:
    1.运行循环
    2.跑圈

    基本作用(重要)

    1.保持程序的持续运行。
    2.处理App中的各种事件(比如触摸事件、定时器事件、Selector事件)。
    3.节省CPU资源,提高程序性能:该做事时做事,该休息时休息。
    ......

    • 一、如果没有RunLoop
      runloop.png

    没有RunLoop的情况下:第3行后程序就结束了。

    • 二、如果有了RunLoop
      runloop1.png

    有RunLoop的情况下:
    由于main函数里面启动了个RunLoop,所以程序并不会马上退出,保持持续运行状态。

    // 用DefaultMode启动
    // RunLoop的主函数,是一个死循环
    void CFRunLoopRun(void) {   /* DOES CALLOUT */
        int32_t result;
        do {
            
            //CFRunLoopRunSpecific具体处理runloop的运行情况
            //CFRunLoopGetCurrent()  当前runloop对象
            //kCFRunLoopDefaultMode  runloop的运行模式的名称
            //1.0e10                 runloop默认的运行时间,即超时为10的九次方
            //returnAfterSourceHandled 回调处理
            result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
            CHECK_FOR_FORK();
            
            //如果runloop没有停止且没有结束则继续循环
        } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
    }
    
    • 三、main函数中的RunLoop
      runloop2.png

    第14行代码的UIApplicationMain函数内部就启动了一个RunLoop
    1.所以UIApplicationMain函数一直没有返回,保持了程序的持续运行。
    2.这个默认启动的RunLoop是跟主线程相关联的。

    RunLoop对象
    • iOS中有2套API来访问和使用RunLoop:

    1.Foundation:NSRunLoop
    2.Core Foundation:CFRunLoopRef

    1.NSRunLoopCFRunLoopRef都代表着RunLoop对象。
    2.NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API(Core Foundation层面)。

    RunLoop与线程

    1.每条线程都有唯一的一个与之对应的RunLoop对象(字典:线程作为key, RunLoop作为value)
    2.主线程RunLoop已经自动创建好了,子线程的RunLoop需要主动创建
    3RunLoop第一次获取时创建,在线程结束时销毁。

    • 主线程对应的runloop默认已经创建并且开启了,而子线程对应的runloop需要手动创建并开启
    //获得runloop(创建runloop)
    
    // should only be called by Foundation
    // t==0 is a synonym for "main thread" that always works
    CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
        if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
        }
        __CFLock(&loopsLock);
        if (!__CFRunLoops) {
            __CFUnlock(&loopsLock);
            // 创建字典
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
            // 创建主线程对应的runloop
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
            // 使用字典保存主线程-主线程对应的runloop
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
            
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        CFRelease(mainLoop);
            __CFLock(&loopsLock);
        }
        
        // 从字典中获取子线程的runloop
        CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        __CFUnlock(&loopsLock);
        if (!loop) {
            // 如果子线程的runloop不存在,那么就为该线程创建一个对应的runloop
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
            __CFLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
            // 把当前子线程和对应的runloop保存到字典中
        if (!loop) {
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
            // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
            __CFUnlock(&loopsLock);
        CFRelease(newLoop);
        }
        if (pthread_equal(t, pthread_self())) {
            _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
            if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
                _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
            }
        }
        return loop;
    }
    
    #import "ViewController.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        //OC语言的API
        //01 获得主线程对应的runloop对象 主运行循环
        NSRunLoop *mainRunloop = [NSRunLoop mainRunLoop];
        //NSLog(@"%@",mainRunloop);
        
        //02 获得当前的runloop对象
        NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];
        NSLog(@"%p---%p",currentRunloop,mainRunloop);
        
        
        //C语言的API
        //01 主运行循环
        CFRunLoopRef mainRunloopRef = CFRunLoopGetMain();
        //02 当前的运行循环
        CFRunLoopRef currentRunloopRef = CFRunLoopGetCurrent();
        NSLog(@"%p-%p",mainRunloopRef,currentRunloopRef);
        
        //转换
        NSLog(@"%p--%p",mainRunloop.getCFRunLoop,mainRunloopRef);
        
        [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
    }
    
    -(void)run{
        NSLog(@"run---%@",[NSThread currentThread]);
        //子线程调用
        //NSLog(@"%@",[NSRunLoop currentRunLoop]);
        
        //runloop和线程的关系
        //(1) 一一的对应的关系(字典)
        //(2) 主线程对应的runloop默认已经创建并且开启了,而子线程对应的runloop需要手动创建并开启
        //(3) 线程销毁,那么runloop也将销毁
        
        //获得子线程对应的runloop |currentRunLoop该方法本身是懒加载的,如果是第一调用那么会创建当前线程对应的runloop并保存,以后调用则直接获取
        NSRunLoop *newThreadRunloop = [NSRunLoop currentRunLoop];
        NSLog(@"%@",newThreadRunloop);
        
        [newThreadRunloop run]; //开启runloop (该runloop开启后马上退出了)
    因为在该子线程中的运行模式是nil,该runloop开启后马上退出了.
    }
    @end
    
    

    注意:runloop在启动时,要选择一种运行模式,之后判断这个运行模式是否为nil判断运行模式中的source、timer是否为nil,如果为nil,则该运行模式就为nil),若所选的运行模式为nil,这个runloop就会马上退出;若所选的运行模式中的source\timer不为nilrunloop就会起来一直运行(死循环)。

    • 获得RunLoop对象

    Foundation:
    1.[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
    2.[NSRunLoop mainRunLoop];// 获得主线程的RunLoop对象

    Core Foundation
    1.CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
    2.CFRunLoopGetMain();// 获得主线程的RunLoop对象

    • RunLoop相关类(核心)

    Core Foundation中关于RunLoop5个类
    1.CFRunLoopRef(RunLoop本身)
    2.CFRunLoopModeRef(RunLoop运行模式)
    3.CFRunLoopSourceRef(输入源事件模式)
    4.CFRunLoopTimerRef(定时器时间)
    5.CFRunLoopObserverRef(观察者)

    runloop3.png

    图很重要

    • CFRunLoopModeRef

    一、CFRunLoopModeRef代表RunLoop运行模式
    1.一个 RunLoop 包含若干个 Mode(运行模式),每个Mode(运行模式)又包含若干个Source/Timer/Observer
    2.每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
    3.如果需要切换Mode,只能退出Loop再重新指定一个Mode进入(该过程是自动进行)。
    4.这样做主要是为了分隔开不同组Source/Timer/Observer,让其互不影响

    二、系统默认注册了5Mode:
    1.kCFRunLoopDefaultModeApp的默认Mode,通常主线程是在这个Mode下运行。
    2.UITrackingRunLoopMode界面跟踪 Mode,用于 ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode 影响。
    3.UIInitializationRunLoopMode: 在刚启动 App时,进入的第一个 Mode启动完成后不再使用
    4.GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
    5.kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode。

    
    #import "ViewController.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        
        
    //    [self timer1];
    //    [self timer2];
    //子线程中开启定时器
    [self performSelectorInBackground:@selector(timer2) withObject:nil];
    }
    
    -(void)timer1
    {
        //01 创建定时器对象
        NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
        
        //02 添加到runloop中
        //Mode :runloop的运行模式(5-默认|界面跟踪|占位)
        //把定时器对象添加到runloop中,并指定运行模式为默认:(只有当运行模式为NSDefaultRunLoopMode的时候,定时器才会工作)
        //[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
        
        //当滚动textView的时候,主运行循环会切换运行模式(默认->界面追踪运行模式)
        //把定时器对象添加到runloop中,并指定运行模式为界面跟踪:(只有当运行模式为NUITrackingRunLoopMode的时候,定时器才会工作)
        //[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
        
        //要求不管有没有在滚动控件,定时器都能够正常工作
        //[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
        //[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
        
        //把定时器对象添加到runloop中,并指定运行模式为commonModes:
        //只有当运行模式为被标记为commonModes的运行模式的时候,定时器才会工作
        //被标记为commonModes运行模式:UITrackingRunLoopMode | kCFRunLoopDefaultMode
        /*
        common modes = <CFBasicHash 0x7fa4c9d00d70 [0x10a3f17b0]>{type = mutable set, count = 2,
            entries =>
            0 : <CFString 0x10b2ed270 [0x10a3f17b0]>{contents = "UITrackingRunLoopMode"}
            2 : <CFString 0x10a411b60 [0x10a3f17b0]>{contents = "kCFRunLoopDefaultMode"}
        }*/
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    }
    
    -(void)timer2
    {
        NSLog(@"timer++++++%@",[NSThread currentThread]);
        
        //定时器工作
        //该方法内部会自动将创建的定时器对象添加到当前的runloop,并且指定运行模式为默认
        [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    
        //手动创建子线程对应的runloop
        [[NSRunLoop currentRunLoop] run];
    }
    
    -(void)run
    {
        NSLog(@"run----%@",[NSRunLoop currentRunLoop].currentMode);
    }
    
    • CFRunLoopSourceRef是事件源(输入源)

    1、以前的分法:
    Port-Based Sources
    Custom Input Sources
    Cocoa Perform Selector Sources

    runloop4.png

    2、现在的分法
    Source0非基于Port的(用户手动触发的:如按钮的点击事件)。
    Source1基于Port的(系统触发的事件)。

    • CFRunLoopTimerRef(定时器)

    1.CFRunLoopTimerRef是基于时间的触发器。
    2.基本上说的就是NSTimer

    • CFRunLoopObserverRef(观察者)

    1.CFRunLoopObserverRef是观察者,能够监听RunLoop状态改变。
    2.可以监听的时间点有以下几个:
    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
    };

    
    #import "ViewController.h"
    
    @interface ViewController ()
    
    @end
    
    @implementation ViewController
    
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
        //01 创建观察者对象
      
        /**
         * 参数说明
         * 第一个参数:分配存储空间(默认:CFAllocatorGetDefault())
         * 第二个参数:要监听的状态
         * 第三个参数:是否要持续监听 YES
         * 第四个参数:默认0
         * 第五个参数:block回调,当RunLoop状态改变的时候会调用
         */
        
        CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    
            /*
             kCFRunLoopEntry = (1UL << 0),
             kCFRunLoopBeforeTimers = (1UL << 1),
             kCFRunLoopBeforeSources = (1UL << 2),
             kCFRunLoopBeforeWaiting = (1UL << 5),
             kCFRunLoopAfterWaiting = (1UL << 6),
             kCFRunLoopExit = (1UL << 7),
             kCFRunLoopAllActivities = 0x0FFFFFFFU
             */
            switch (activity) {
                case kCFRunLoopEntry:
                    NSLog(@"runloop进入");
                    break;
                case kCFRunLoopBeforeTimers:
                    NSLog(@"即将处理timer事件");
                    break;
                case kCFRunLoopBeforeSources:
                    NSLog(@"即将处理source事件");
                    break;
                case kCFRunLoopBeforeWaiting:
                    NSLog(@"即将进入休眠");
                    break;
                case kCFRunLoopAfterWaiting:
                    NSLog(@"被唤醒");
                    break;
                case kCFRunLoopExit:
                    NSLog(@"runloop退出");
                    break;
    
                default:
                    break;
            }
        });
    
        //02 监听当前runloop的运行状态
        /**
            * 参数说明
            * 第一个参数:RunLoop对象
            * 第二个参数:监听者
            * 第三个参数:RunLoop在哪种运行模式下的状态
            *          NSDefaultRunLoopMode == kCFRunLoopDefaultMode
            *          NSRunLoopCommonModes == kCFRunLoopCommonModes
            */
        
        CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    
        [NSTimer scheduledTimerWithTimeInterval:4.0 target:self selector:@selector(timerRun) userInfo:nil repeats:YES];
    }
    
    -(void)timerRun
    {
        NSLog(@"处理收到的事件--timer--RUN");
    }
    
    @end
    
    
    • 网友分析RunLoop处理逻辑图


      runloop5.png
    • 官方RunLoop处理逻辑过程


      runloop6.png
    • 代码模拟RunLoop

    #import <Foundation/Foundation.h>
    
    void msg(int n)
    {
        NSLog(@"runloop被唤醒");
        NSLog(@"<<<<<<< 处理收到的任务 >>>>>>>>");
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
    
            do {
    
                NSLog(@"有事件需要我处理吗?");
                NSLog(@"没有的话我就休息了!");
                NSLog(@"Runloop进入休眠.....zzz");
    
                int number = 0;
                scanf("%d",&number);
                msg(number);
    
            } while (1);
    
        }
        return 0;
    }
    
    • RunLoop在iOS开发中的应用
      1、该方法会受到RunLoop的影响。
     [ performSelector:<#(nonnull SEL)#> withObject:<#(nullable id)#> afterDelay:<#(NSTimeInterval)#> ];
    
    #import "ViewController.h"
    
    @interface ViewController ()
    @property (weak, nonatomic) IBOutlet UIImageView *imageView;
    
    @end
    
    @implementation ViewController
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    
        //该方法内部会自动把事件添加到当前的RunLoop中,并且指定运行模式为默认模式(kCFRunLoopDefaultMode),在滚动TextView的时候没有切换到滚动模式(UITrackingRunLoopMode)
        
    //    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:3.0];
        
    //    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:3.0 inModes:@[NSRunLoopCommonModes]];
        
        [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode,UITrackingRunLoopMode]];
        
    }
    
    @end
    

    2、线程保活(开启一条常驻的子线程)

    #import "ViewController.h"
    
    @interface ViewController ()
    
    @property (strong, nonatomic)NSThread *thread;
    
    @end
    
    @implementation ViewController
    
    - (IBAction)createThreadBtnClick:(id)sender {
        
        //01创建线程
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run1) object:nil];
        
        //02启动线程
        [thread start];
        
        self.thread = thread;
        
    }
    
    - (IBAction)goOnBtnClick:(id)sender {
    
        //让之前创建的子线程继续执行任务,主线程->子线程
        
        [self performSelector:@selector(run2) onThread:self.thread withObject:nil waitUntilDone:YES];
        
    }
    
    //该方法执行完毕,线程对象就会进入到死亡状态
    - (void)run1{
        
        NSLog(@"run1 ------ %@",[NSThread currentThread]);
        
        //01子线程的runLoop需要手动创建 + 启动
        //02runLoop启动之后,选择运行模式(默认),内部会判断运行模式是否为空
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        
        //03往运行模式中添加事件(source|timer)
    //    [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(timerRun) userInfo:nil repeats:YES];
        //    source(port|timer|selector)
        
        //04为默认的运行模式添加port事件,目的是让运行模式不为空,把RunLoop开启起来
    //    NSPort *port= [[NSPort alloc] init];
        NSPort *port = [NSPort port];
        [runLoop addPort:port forMode:NSRunLoopCommonModes];
        
        [runLoop run];//内部指定的原型模式为默认(NSDefaultRunLoopMode)
        
        NSLog(@"end ---------");
        
    }
    
    - (void)run2{
        
        NSLog(@"run2 ------ %@",[NSThread currentThread]);
    }
    
    - (void)timerRun{
        
        NSLog(@"%s",__func__);
        
    }
    
    @end
    
    
    • 有关RunLoop内部的自动释放池

    a.自动释放池第一次创建:是在 RunLoop 启动的时候。
    b.自动释放池最后一次销毁:是在 RunLoop 退出的时候。
    c.自动释放池其他事件的创建和销毁:是在 RunLoop 即将进入到休眠的时候,会把之前的自动释放池销毁,重新创建一个自动释放池。

    RunLoop相关知识希望给朋友们带来一丢丢的帮助,如有问题敬请批正!!

    相关文章

      网友评论

        本文标题:iOS 开发之RunLoop

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