美文网首页
RunLoop详情

RunLoop详情

作者: 邓布利多教授 | 来源:发表于2019-03-12 09:55 被阅读0次

    RunLoop简介

    运行循环,在程序运行过程中循环做一些事情,如果没有Runloop程序执行完毕就会立即退出,如果有Runloop程序会一直运行,并且时时刻刻在等待用户的输入操作。RunLoop可以在需要的时候自己跑起来运行,在没有操作的时候就停下来休息。充分节省CPU资源,提高程序性能。

    RunLoop作用

    1、保持程序持续运行
    2、处理App中的各种事件
    3、节省CPU资源,提高程序性能

    RunLoop和线程间的关系

    1、每条线程都有唯一的一个与之对应的RunLoop对象
    2、RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
    3、主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建
    4、RunLoop在第一次获取时创建,在线程结束时销毁

    RunLoop的结构

    主要的成员变量:
    CFMutableSetRef _sources0;//基于Port的线程间通信
    CFMutableSetRef _sources1;//触摸事件,PerformSelectors
    CFMutableArrayRef _observers;//定时器,NSTimer
    CFMutableArrayRef _timers;//监听器,用于监听RunLoop的状态

    一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer

    RunLoop相关类及作用

    CFRunLoopRef:获得当前RunLoop和主RunLoop
    CFRunLoopModeRef:RunLoop 运行模式,只能选择一种,在不同模式中做不同的操作
    CFRunLoopSourceRef: 事件源,输入源
    CFRunLoopTimerRef:定时器时间
    CFRunLoopObserverRef:观察者

    CFRunLoopModeRef的5种模式

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

    CFRunLoopModeRef的应用

    解决在ScrollView中,滑动控制器时候定时器失效的方案

    /*
    创建定时器
    */
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
    
    /*
    把定时器加入到runloop中
    
    *重点是Mode*
    
    1、如果Mode使用NSDefaultRunLoopMode:在ScrollView滑动的时候,定时器就会失效,停止滑动的时候又会恢复
    2、如果Mode使用UITrackingRunLoopMode:在ScrollView滑动的时候,定时器是正常的,停止滑动的时候就会失效
    
    原因是:当ScrollView滑动的时候,RunLoop的Mode会自动切换成UITrackingRunLoopMode模式,因此timer失效;当停止滑动,RunLoop又会切换回NSDefaultRunLoopMode模式,因此timer又会重新启动。
    
    为什么使用NSRunLoopCommonModes模式就可以滑动和不滑动的时候都正常了呢?因为NSRunLoopCommonModes是一个占位用的Mode,用来标记UITrackingRunLoopMode和kCFRunLoopDefaultMode。因此UITrackingRunLoopMode和kCFRunLoopDefaultMode被标记之后的作用就相同了。
    
    */
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    
    

    CFRunLoopObserverRef的应用

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

    //创建监听者
         /*
         第一个参数 CFAllocatorRef allocator:分配存储空间 CFAllocatorGetDefault()默认分配
         第二个参数 CFOptionFlags activities:要监听的状态 kCFRunLoopAllActivities 监听所有状态
         第三个参数 Boolean repeats:YES:持续监听 NO:不持续
         第四个参数 CFIndex order:优先级,一般填0即可
         第五个参数 :回调 两个参数observer:监听者 activity:监听的事件
         */
        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);
         /*
         CF的内存管理(Core Foundation)
         凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release
         GCD本来在iOS6.0之前也是需要我们释放的,6.0之后GCD已经纳入到了ARC中,所以我们不需要管了
         */
        CFRelease(observer);
    
    • CFOptionFlags activities系统提供的枚举
    kCFRunLoopEntry = (1UL << 0),   //   即将进入RunLoop
    kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source
    kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),// 刚从休眠中唤醒
    kCFRunLoopExit = (1UL << 7),// 即将退出RunLoop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
    

    RunLoop退出

    主线程销毁RunLoop退出
    Mode中有一些Timer 、Source、 Observer,这些保证Mode不为空时保证RunLoop没有空转并且是在运行的,当Mode中为空的时候,RunLoop会立刻退出
    我们在启动RunLoop的时候可以设置什么时候停止

    子线程开启RunLoop

    /*
    创建子线程并开启
    */
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(show) object:nil];
    [thread start];
    
    /*
    在子线程中创建RunLoop
    */
    -(void)show{
        // 注意:打印方法一定要在RunLoop创建开始运行之前,如果在RunLoop跑起来之后打印,RunLoop先运行起来,已经在跑圈了就出不来了,进入死循环也就无法执行后面的操作了。
        // 但是此时点击Button还是有操作的,因为Button是在RunLoop跑起来之后加入到子线程的,当Button加入到子线程RunLoop就会跑起来
        NSLog(@"%s",__func__);
        // 1.创建子线程相关的RunLoop,在子线程中创建即可,并且RunLoop中要至少有一个Timer 或 一个Source 保证RunLoop不会因为空转而退出,因此在创建的时候直接加入
        // 添加Source [NSMachPort port] 添加一个端口
        [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        // 添加一个Timer
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];    
        //创建监听者
        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添加监听者
        CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
        // 2.子线程需要开启RunLoop
        [[NSRunLoop currentRunLoop]run];
        CFRelease(observer);
    }
    

    自动释放池

    RunLoop内部有一个自动释放池,当RunLoop开启时,就会自动创建一个自动释放池,当RunLoop在休息之前会释放掉自动释放池的东西,然后重新创建一个新的空的自动释放池,当RunLoop被唤醒重新开始跑圈时,Timer,Source等新的事件就会放到新的自动释放池中,当RunLoop退出的时候也会被释放。

    重点

    创建子线程相关的RunLoop,RunLoop中要至少有一个Timer 或 一个Source 保证RunLoop不会因为空转而退出,因此在创建的时候直接加入,如果没有加入Timer或者Source,或者只加入一个监听者,运行程序会崩溃

    相关文章

      网友评论

          本文标题:RunLoop详情

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