RunLoop

RunLoop

顾名思义

  • 运行循环
  • 在程序运行过程中循环做一些事情
  • 一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑是这样的:
function loop() {
    initialize();
    do {
        var message = get_next_message();
        process_message(message);
    } while (message != quit);
}

这种模型通常被称作Event Loop。
实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立即被唤醒。

RunLoop实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面的Event Loop的逻辑。线程执行了这个函数后,就会一直处于正常函数内部“接受消息->等待->处理”的循环中,直到这个循环结束(比如传入quit的消息),函数返回。


应用范畴

  • 定时器(Timer)、performSelector
  • GCD Async Main Queue
  • 事件响应、手势识别、界面刷新
  • 网络请求
  • AutoreleasePool

RunLoop的基本作用

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

RunLoop对象

  • iOS中有2套API来访问和使用RunLoop
    • Foundation:NSRunLoop
    • Core Foundation:CFRunLoopRef
  • NSRunLoop和CFRunLoopRef都代表着RunLoop对象
    • NSRunLoop是基于CFRunLoopRef的一层OC包装(所以runloop的地址会不一样
      • 提供了面向对象的API,但是这些API不是线程安全的
    • CFRunLoopRef是开源的
      • 提供了纯C函数的API,所有这些API都是线程安全的
    // OC获取当前的RunLoop
    NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];
    // OC获取主线程RunLoop
    NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
    // C语言获取当前的RunLoop
    CFRunLoopRef currentRunloop2 = CFRunLoopGetCurrent();
    // C语言获取主线程的RunLoop
    CFRunLoopRef mainRunloop2 = CFRunLoopGetMain();

RunLoop与线程

  • 每条线程都有唯一的一个与之对应的RunLoop对象
  • RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
  • 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
  • RunLoop会在线程结束时销毁
  • 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
  • 只能在一个线程的内部获取其RunLoop(主线程除外)。

RunLoop相关的类

  • Core Foundation中关于RunLoop的5个类
    • CFRunLoopRef
    • CFRunLoopModeRef
    • CFRunLoopSourceRef 事件产生的地方
    • CFRunLoopTimerRef
    • CFRunLoopObserverRef

CFRunLoopRef

typedef struct __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
    pthread_t _pthread; // RunLoop所对应的线程
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode; // _modes这个集合里面只有一个模式被称为当前模式_currentMode
    CFMutableSetRef _modes; // 这个集合里面装了很多CFRunLoopModeRef类型的对象
};  

CFRunLoopModeRef

  • CFRunLoopModeRef代表RunLoop的运行模式
  • 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
  • RunLoop启动时只能选择其中一个Mode,作为currentMode
  • 如果需要切换Mode,只能退出当前Loop,在重新选择一个Mode进入
    • 目的:不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
  • 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
常见的2种Mode
  • kCFRunLoopDefaultMode(等价于NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
  • UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
  • 另外还有一种模式:kCFRunLoopCommonModes 默认包括kCFRunLoopDefaultMode、UITrackingRunLoopMode两种模式
typedef struct __CFRunLoopMode * CFRunLoopModeRef;
struct __CFRunLoopMode {
    CFStringRef _name; // mode的名称
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1; // source里面装了很多CFRunLoopSourceRef类型的对象
    CFMutableArrayRef _observers;// 里面装了很多CFRunLoopObserverRef类型的对象
    CFMutableArrayRef _timers; // 里面装了很多CFRunLoopTimerRef类型的对象
};

CFRunLoopObserverRef

/* RunLoop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),// 即将进入Loop
    kCFRunLoopBeforeTimers = (1UL << 1),// 即将处理Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
    kCFRunLoopExit = (1UL << 7), // 即将退出Loop
    kCFRunLoopAllActivities = 0X0FFFFFFFU
};
// 监听事件的状态变化(只有c语言接口)
// 方法一
void observeRunLoopActivities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"kCFRunLoopBeforeTimers");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"kCFRunLoopBeforeSources");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"kCFRunLoopBeforeWaiting");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"kCFRunLoopAfterWaiting");
            break;
        case kCFRunLoopExit:
            NSLog(@"kCFRunLoopExit");
            break;
            
        default:
            break;
    }
}

    // 创建observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActivities, NULL);
    // 添加observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 释放observer
    CFRelease(observer);
    
// 方法二
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry: {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopEntry - %@",mode);
                CFRelease(mode);
                break;
            }
            case kCFRunLoopExit:{
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopExit- %@",mode);
                CFRelease(mode);
                break;
            }
                
            default:
                break;
        }
    });
    // 添加observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 释放observer
    CFRelease(observer);

根据源码分析,得到如下关系图

runloop

RunLoop的运行逻辑

  • Source0
    • 触摸事件处理
    • performSelector: onThread:(提供一个线程,就可以让对应方法去该线程去执行)
  • Source1
    • 基于Port的线程间通信
    • 系统事件捕捉
  • Timers
    • NSTimer
    • performSelector: withObject: afterDelay:
  • Observers
    • 用于监听RunLoop的状态
    • UI刷新(BeforeWaiting)
    • AutoReleasePool(BeforeWaiting)

如下图所示


runloop流程

RunLoop休眠的实现原理

休眠的实现原理

实质:从用户态切换到内核态,去等待消息,没有消息就让线程休眠,不在占用cpu,有消息就唤醒线程。

RunLoop在实际开发中的应用

  • 控制线程生命周期(线程保活,比如AFNetWorking都是在子线程中等待的,所以是一直保活)

  • 解决NSTimer在滑动时停止工作的问题

static int count = 0;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"%d",++count);
    }];
// NSDefaultRunLoopMode/UITrackingRunLoopMode才是真正存在的模式    
// NSRunLoopCommonModes并不是一个真的模式,它只是一个标记
// timer能在_commonModes数组中存放的模式下工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//    [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
//        NSLog(@"%d",++count);
//    }];
  • 监控应用卡顿
  • 性能优化

你可能感兴趣的:(RunLoop)