RunLoop的基本认识

RunLoop的介绍

  • 什么是RunLoop?
  • Xcode执行程序的时候就先从Main函数那里开始执行的,Main函数里面实现了UIApplicationMain,而UIApplicationMain里面启动了一个RunLoop对象,所以程序才能一直运行,如果没有RunLoop程序一开始就挂了
  • RunLoop内部其实是一个do while 循环 (死循环) 有事件需要处理的时候通知RunLoop 没有事件的时候就让它进入休眠状态
RunLoop的基本认识_第1张图片
示例.png

RunLoop的基本作用

  • 保持程序的持续运行
  • 处理App中的各种事件(如:触摸事件,定时器事件)
  • 节省CPU资源,提高程序性能,(如:程序没有任务就进入睡眠状态,有任务的时候就唤醒)

iOS程序中又两套API来访问和使用RunLoop

1.C语言:Core Foundation -> CFRunLoopRef
2.OC语言的:Foundation -> NSRunLoop
-NSRunLoop 其实是 CFRunLoopRef 的OC包装

  • RunLoop与线程
    1.关系:一个RunLoop对应着一条唯一的线程
    2,创建:主线程RunLoop已经创建好了,子线程的RunLoop需要手动创建
    3,生命周期:RunLoop在第一次获取时创建,在线程结束时销毁
#如果想让线程不死,可以手动创建一个RunLoop
void msg(int number)
{
    NSLog(@"runloop被唤醒");
    NSLog(@"执行任务---%d",number);
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        do {
            
            NSLog(@"是否有任务需要处理,没有就进入休眠状态");
            NSLog(@"runloop进入到休眠状态");
            int number = 0;
            scanf("%d",&number);
            msg(number);
            
        } while (1);
    }
    return 0;
}

[NSRunLoop currentRunLoop]; //当前的RunLoop
[NSRunLoop mainRunLoop]; //主线程的RunLoop
在子线程需要手动创建RunLoop 创建当前的RunLoop即可
RunLoop是懒加载的
RunLoop是用字典来存储的

RunLoop的相关类

  # 五个相关的类
    a.CFRunloopRef
    b.CFRunloopModeRef【Runloop的运行模式】
    c.CFRunloopSourceRef【Runloop要处理的事件源】
    d.CFRunloopTimerRef【Timer事件】
    e.CFRunloopObserverRef【Runloop的观察者(监听者)】
RunLoop的基本认识_第2张图片
关系图.png

Mode是运行模式
一个RunLoop包含了若干个模式;
一个运行模型至少要有一个source 或者 Timer
每次RunLoop启动时,只能指定其中一个Mode,这个Mode被称作CurrentMode
如果需要切换Mode ,只能退出线程,重新指定一个Mode进入

  • 这样做主要是为了分隔开不同组的 source/Timer/Observer,让它们互不影响

  • 系统默认注册了五种Mode

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

CFRunloopSourceRef (source事件源)

source0 : 非基于端口的
source1: 基于端口的
可以通过打断点的方式查看一个方法的函数调用栈

CFRunloopTimerRef (Timer事件)

  • NSTimer收RunLoop的Mode的影响
    • 对应Mode处理对应的事件,增加用户的体验
/*
    说明:
    (1)runloop一启动就会选中一种模式,当选中了一种模式之后其它的模式就都不鸟。一个mode里面可以添加多个NSTimer,也就是说以后当创建NSTimer的时候,可以指定它是在什么模式下运行的。
    (2)它是基于时间的触发器,说直白点那就是时间到了我就触发一个事件,触发一个操作。基本上说的就是NSTimer
*/
- (void)timer1
{
    //NSTimer 调用了scheduledTimer方法,那么会自动添加到当前的runloop里面去,而且runloop的运行模式kCFRunLoopDefaultMode

    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

    //更改模式
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

}

- (void)timer2
{
    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

    //定时器添加到UITrackingRunLoopMode模式,一旦runloop切换模式,那么定时器就不工作
    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

    //定时器添加到NSDefaultRunLoopMode模式,一旦runloop切换模式,那么定时器就不工作
    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

    //占位模式:common modes标记
    //被标记为common modes的模式 kCFRunLoopDefaultMode  UITrackingRunLoopMode
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

    //    NSLog(@"%@",[NSRunLoop currentRunLoop]);
}

- (void)run
{
    NSLog(@"---run---%@",[NSRunLoop currentRunLoop].currentMode);
}

- (IBAction)btnClick {

    NSLog(@"---btnClick---");
}

GCD定时器不受RunLoop的Mode的影响

GCD定时器的特点:
1,GCD的定时器不会受到RunLoop运行模式的影响
2,可以控制热舞在主线程还是子线程执行
3,GCD定时器比NSTimer更加准确是因为单位不同:GCD单位是纳秒

//1.创建定时器对象
    /*
     第一个参数:DISPATCH_SOURCE_TYPE_TIMER 创建的是一个定时器
     第四个参数:队列,决定在哪个线程中执行任务
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
    
    dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, 3.0 *NSEC_PER_SEC);
    
    //2.设置定时器(间隔时间|开始时间)
    /*
     第一个参数:定时器对象
     第二个参数:从什么时候开始计时 DISPATCH_TIME_NOW == 从现在开始
     第三个参数:间隔时间 2.0 以纳秒为单位
     第四个参数:精准度(表示允许的误差)== 0
     */
    dispatch_source_set_timer(timer, t, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    
    //3.定义定时器的工作
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"----GCD----%@",[NSThread currentThread]);
    });
    
    //4.启动执行
    dispatch_resume(timer);

        //注意:dispatch_source_t本质上是OC类,在这里是个局部变量,需要强引用
    self.timer = timer;
.

CFRunloopObserverRef(observer观察者,监听者)

作用: 监听运行循环的状态
如何监听:
1,创建监听对象
2,给RunLoop添加监听者
3,注意对象的释放

 //1.创建监听者
    /*
     第一个参数:分配存储空间
     第二个参数:要监听runloop的什么状态
     第三个参数:持续监听 == YES
     第四个参数:传0
     */
    CFRunLoopObserverRef observer =
CFRunLoopObserverCreateWithHandler
(CFAllocatorGetDefault(),kCFRunLoopAllActivities, YES, 0, 
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        
        /*
         kCFRunLoopEntry = (1UL << 0),         runloop启动
         kCFRunLoopBeforeTimers = (1UL << 1),  runloop即将处理定时器事件
         kCFRunLoopBeforeSources = (1UL << 2), runloop即将处理source事件
         kCFRunLoopBeforeWaiting = (1UL << 5), runloop即将休眠
         kCFRunLoopAfterWaiting = (1UL << 6),  runloop被唤醒
         kCFRunLoopExit = (1UL << 7),          退出
         kCFRunLoopAllActivities = 0x0FFFFFFFU
         */
        
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"runloop启动");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"runloop即将处理定时器事件");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"runloop即将处理source事件");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"runloop即将休眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"runloop被唤醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"runloop退出");
                break;
                
            default:
                break;
        }
    });
    
    
    //2.设置监听的runloop和运行模式
    /*
     第一个参数:runloop对象
     第二个参数:监听者
     第三个参数:runloop的运行模式
     第四个参数:
     */
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    
    //3.需要释放
    CFRelease(observer);
RunLoop的基本认识_第3张图片

RunLoop处理逻辑-官方.png

上图显示了线程的输入源

1.基于端口的输入源(Port Sources)
2.自定义输入源(Custom Sources)
3.Cocoa执行Selector的源(performSelectorxxxx
方法)
4.定时源(Timer Sources )

线程针对上面不同的输入源,有不同的处理机制

1.handlePort——处理基于端口的输入源
2.customSrc——处理用户自定义输入源
3.mySelector——处理Selector的源
4.timerFired——处理定时源

RunLoop的应用场

RunLoop的基本认识_第4张图片
应用场景.png
  • 常驻线程

  • 比如网络请求,语音消息,可以创建一个子线程专门处理语音消息的代码

  • 一直在后台做耗时操作,不想让子线程销毁,那么久用一个do while 循环,在do while循环管里面处理事件

  • 自动释放池什么时候释放?

  • 启动RunLoop的时候会创建自动释放池,RunLoop退出的时候就会让自动释放池销毁

  • 当RunLoop进入休眠的时候,就会让自动释放池之前的销毁,然后重新创建一个新的自动释放池(因为在休眠状态,没有事情做,所以自动释放池也没用)

#Author: 会跳舞的狮子

你可能感兴趣的:(RunLoop的基本认识)