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的状态改变。

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

原文地址:https://www.cnblogs.com/lyz0925/p/11544344.html