• RunLoop的知识小记


    RunLoop字面上的意思是,运行循环;

    其基本作用:保持程序的持续运行;

          处理App中的各种事件(比如:触摸事件、定时器事件、Selector事件)

          节省CPU资源,提高程序性能:该做事时做事,该休息时休息

    1.main函数中的RunLoop

    int main(int argc, char * argv[]) {
        @autoreleasepool {
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    
    
    在这个main函数中,UIApplicationMain函数内部就启动了一个RunLoop,所以UIApplicationMain函数一直没有返回,保持了程序的持续运行。这个默认启动的RunLoop是跟主线程相关联的。

    NSRunLoop 是基于CFRunLoopRef的一层OC包装,所以了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API。

    2.RunLoop与线程的关系

    每条线程都有唯一的一个与之对应的RunLoop对象
    主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建
    RunLoop在第一次获取时创建,在线程结束时销毁
    获取子线程对应的RunLoop(即,currentRunLoop)该方法本身是懒加载的,如果第一次调用就会创建当前线程对应的RunLoop并保存,以后调用则直接获取

    3.RunLoop的获取

    #pragma mark - RunLoop
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        //OC语言中的API
        //01 获取主线程对应的runloop对象
        NSRunLoop *mainRunloop = [NSRunLoop mainRunLoop];
        //02 获取当前的runloop的对象
        NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];
        NSLog(@"%p---%p", mainRunloop, currentRunloop);
        
        //C语言的API
        //01 主运行循环
        CFRunLoopRef mainRunloopRef = CFRunLoopGetMain();
        //02 当前的运行循环
        CFRunLoopRef currentRunloopRef = CFRunLoopGetCurrent();
        NSLog(@"%p---%p", mainRunloopRef, currentRunloopRef);
        
        //转化
        NSLog(@"%p----%p", mainRunloop.getCFRunLoop, mainRunloopRef);
    }

    打印的结果:

    2019-09-18 15:31:31.176193+0800 NSCach[60622:1117017] 0x60000184c060---0x60000184c060
    2019-09-18 15:31:31.176302+0800 NSCach[60622:1117017] 0x600000050600---0x600000050600
    2019-09-18 15:31:31.176362+0800 NSCach[60622:1117017] 0x600000050600----0x600000050600

    可以看出,当前的主运行循环和当前运行循环是同一个runloop, 最后一行可以看出,OC获取的runloop与C的runloop可以相互转化。

    4.RunLoop的模式与Timer的关系

    //Runloop的运行模式和Timer
    - (void)RunLoopModeAndTimer {
        //创建定时器对象
        NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
        //添加到runloop中
        //Mode:runloo的运行模式(默认|界面跟踪|占位)
        //把定时器对象添加到runloop中,并制定运行模式为默认--当运行模式为NSDefaultRunLoopMode的时候,定时器才会工作
        //[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
        
        //当滚动textView的时候,主运行循环会切换运行模式:从默认切换成界面跟踪运行模式
        //把定时器对象添加到runloop中,并制定运行模式为界面跟踪--当运行模式为UITrackingRunloopMode的时候(即拖拽界面时),定时器才会工作
        //[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
        
        //当两种模式都可以运行时,均可用
        //把定时器对象添加到runloop中,并制定运行模式为NSRunLoopCommonModes--当运行模式为NSDefaultRunLoopMode或者UITrackingRunloopMode的时候,定时器才会工作
        //需要注意没有NSRunLoopCommonModes这种模式,NSRunLoopCommonModes是标记NSDefaultRunLoopMode或者UITrackingRunloopMode的集合
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    }
    
    - (void)run {
        NSLog(@"run----%@", [NSRunLoop currentRunLoop].currentMode);
    }

    注意

    //该方法内部会自动创建的定时器对象添加到当前的runloop,并且指定运行模式为默认
    //    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    
        //如果想利用上面的那种方法,修改运行模式,可修改成如下
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

    注意:上面添加的定时器,如果是添加在主线程中的,所以,不需要开始runloop,定时器就可以工作;但是如果,直接添加到子线程中,例如下面的代码:

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

        [self performSelectorInBackground:@selector(RunLoopModeAndTimer) withObject:nil];

    }

    - (void)RunLoopModeAndTimer {
    
       //该方法内部会自动创建的定时器对象添加到当前的runloop,并且指定运行模式为默认
       [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    
    }
    
    - (void)run {
        NSLog(@"run----%@", [NSRunLoop currentRunLoop].currentMode);
    }

    那么,点击界面的时候,定时器是不工作的;因为定时器对象添加到当前的runloop中,而当前的runloop在子线程中;子线程中的runloop需要手动创建,所以此时定时器不工作。

    解决方式:开启runloop

    - (void)RunLoopModeAndTimer {
    
       //该方法内部会自动创建的定时器对象添加到当前的runloop,并且指定运行模式为默认
       [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] run];
    }

    5.GCD的定时器

    按照上面那段代码执行后,会发现,timer并没有执行。因为在程序运行到 } 后,代码就执行完了,就被释放了。所以需要添加一个强引用。

    修改如下:

     总结:

    Runloop的相关类

    Core Foundation中关于RunLoop的5个类:
        CFRunLoopRef
        CFRunLoopModeRef
        CFRunLoopSourceRef
        CFRunLoopTimerRef
        CFRunLoopObserverRef

        当runloop选择一个运行模式后,需要判断运行模式是否为空,判断的依据是:Source和Timer是否有数据,如果没有数据,runloop立马退出。

    选择了一个运行模式后

    如果source或者timer有数据时,就开启runloop。

    RunLoopObserver简单说明

      CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变。

      可以监听的时间点有以下几个:

  • 相关阅读:
    ASP.NET Core 发布 centos7 配置守护进程
    AutoMapper在asp.netcore中的使用
    git忽略文件并删除git仓库中的文件
    Animate.css 一款牛逼的css3动画库
    URL中特殊符号的处理
    efcore 配置链接sqlserver
    简单抓取小程序大全,并展示。
    UEditor上传图片到七牛C#(后端实现)
    软件项目管理三国启示录01 群雄争霸之项目经理的自我修养
    【调侃】IOC前世今生
  • 原文地址:https://www.cnblogs.com/lyz0925/p/11544344.html
Copyright © 2020-2023  润新知