1 什么是Runloop

从字面意思看,Runloop的意思就是
运行循环,跑圈

Runloop基本作用:

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

如果没有Runloop

int main(int argc, char * argv[]) {
    NSLog(@"execute main function");//程序开始
    return 0;//程序结束
}

Runloop与线程

每条线程都有唯一的一个与之对应的RunLoop对象

主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建

RunLoop在第一次获取时创建,在线程结束时销毁

2 Runloop对象

iOS中有2套API来访问和使用RunLoop

  1. Foundation
  • NSRunLoop
  1. Core Foundation
  • CFRunLoopRef

NSRunLoop和CFRunLoopRef都代表着RunLoop对象

  • NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API(Core Foundation层面)
  • Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
  • Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象

3 Runloop相关的类

Core Foundation中关于RunLoop的5个类

  • CFRunLoopRef
  • CFRunLoopModeRef 【Runloop的运行模式】
  • CFRunLoopSourceRef 【Runloop要处理的事件源】
  • CFRunLoopTimerRef 【Timer事件】
  • CFRunLoopObserverRef 【Runloop的观察者(监听者)】

CFRunLoopRef

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

系统默认注册了5个Mode:

  • kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
  • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
  • 典型事例就是拖动scrollview时,NSTimer不工作,其原因就是,拖动时runloop切换到界面追踪模式,此时其他模式的事件(例如定时器事件)就无法继续。
  • UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
  • GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
  • kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode

CFRunLoopSourceRef

CFRunLoopSourceRef是事件源(输入源)

  • 以前的分法
  • Port-Based Sources
  • Custom Input Sources
  • Cocoa Perform Selector Sources
  • 现在的分法
  • Source0:非基于Port的
  • Source1:基于Port的

CFRunLoopTimerRef

  • CFRunLoopTimerRef是基于时间的触发器
  • 基本上说的就是NSTimer

CFRunLoopObserverRef

  • CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变
  • 可以监听的时间点有以下几个
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),   //即将进入Runloop
    kCFRunLoopBeforeTimers = (1UL << 1),    //即将处理NSTimer
    kCFRunLoopBeforeSources = (1UL << 2),   //即将处理Sources
    kCFRunLoopBeforeWaiting = (1UL << 5),   //即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),    //刚从休眠中唤醒
    kCFRunLoopExit = (1UL << 7),            //即将退出runloop
    kCFRunLoopAllActivities = 0x0FFFFFFFU   //所有状态改变
};

4 重点-Runloop运行逻辑

ios 开发 runtime 重置方法 ios开发 runloop_iOS

ios 开发 runtime 重置方法 ios开发 runloop_主线程_02

5 重点-Runloop应用

1)NSTimer
2)ImageView显示:控制方法在特定的模式下可用
3)PerformSelector
4)常驻线程:在子线程中开启一个runloop
5)自动释放池的释放时机:
第一次创建:进入runloop的时候
最后一次释放:runloop退出的时候
其它创建和释放:当runloop即将休眠的时候会把之前的自动释放池释放,然后重新创建一个新的释放池

ios 开发 runtime 重置方法 ios开发 runloop_主线程_03

5 重点-Runloop应用

  • 监听Runloop,可以在按钮响应方法之前做些事情
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self observer];
}
-(void)observer
{
    //1.创建监听者
    /*
     第一个参数:怎么分配存储空间
     第二个参数:要监听的状态 kCFRunLoopAllActivities 所有的状态
     第三个参数:时候持续监听
     第四个参数:优先级 总是传0
     第五个参数:当状态改变时候的回调
     */
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {

        /*
         kCFRunLoopEntry = (1UL << 0),        即将进入runloop
         kCFRunLoopBeforeTimers = (1UL << 1), 即将处理timer事件
         kCFRunLoopBeforeSources = (1UL << 2),即将处理source事件
         kCFRunLoopBeforeWaiting = (1UL << 5),即将进入睡眠
         kCFRunLoopAfterWaiting = (1UL << 6), 被唤醒
         kCFRunLoopExit = (1UL << 7),         runloop退出
         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;
        }
    });

    /*
     第一个参数:要监听哪个runloop
     第二个参数:观察者
     第三个参数:运行模式
     */
    CFRunLoopAddObserver(CFRunLoopGetCurrent(),observer, kCFRunLoopDefaultMode);

    //NSDefaultRunLoopMode == kCFRunLoopDefaultMode
    //NSRunLoopCommonModes == kCFRunLoopCommonModes
}

- (IBAction)RunloopObserver:(id)sender {
    NSLog(@"处理点击事件:%s",__func__);
}

效果图

ios 开发 runtime 重置方法 ios开发 runloop_主线程_04

  • 创建一个常驻线程
- (IBAction)createThread:(id)sender {

    //1.创建线程
    self.thread = [[NSThread alloc]initWithTarget:self selector:@selector(task) object:nil];
    [self.thread start];
}
-(void)task
{
    NSLog(@"task---%@",[NSThread currentThread]);
    //    while (1) {
    //       NSLog(@"task1---%@",[NSThread currentThread]);
    //    }
    //解决方法:开runloop
    //1.获得子线程对应的runloop
    NSRunLoop *runloop = [NSRunLoop currentRunLoop];

    //保证runloop不退出
    //NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    //[runloop addTimer:timer forMode:NSDefaultRunLoopMode];
    [runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];

    //2.默认是没有开启
    [runloop run];//开启
//    [runloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];//开启Runloop,到特定时间结束

    NSLog(@"---end----");
}
- (IBAction)doOthertask:(id)sender {
    //[self.thread start];

    [self performSelector:@selector(OtheTask) onThread:self.thread withObject:nil waitUntilDone:YES];
}
-(void)OtheTask
{
    NSLog(@"OtheTask---%@",[NSThread currentThread]);
}

-(void)run
{
    NSLog(@"%s",__func__);
}

//Runloop中自动释放池的创建和释放
//第一次创建:启动runloop
//最后一次销毁:runloop退出的时候
//其他时候的创建和销毁:当runloop即将睡眠的时候销毁之前的释放池,重新创建一个新的

效果图

ios 开发 runtime 重置方法 ios开发 runloop_iOS_05

Runloop源码分析

ios 开发 runtime 重置方法 ios开发 runloop_主线程_06

资料