iOS底层笔记--RunLoop

298 阅读5分钟

本文属笔记性质,是对kirito_song冰凌天两位文章学习和转载

文章具体地址:
小码哥iOS学习笔记第十七天: Runloop基本认识
MJiOS底层笔记--Runloop

什么是RunLoop?

RunLoop是通过内部维护的事件循环来对事件/消息进行管理的一个对象

  • 没有消息需要处理时,休眠以避免资源占用:从用户态到内核态切换
  • 有消息处理时,立刻被唤醒:从内核态到用户态的切换

应用范畴

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

作用

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

RunLoop的数据结构

NSRunloop是CFRunloop的封装,提供了面向对象的API

RunLoop相关的类有五个

  • CFRunLoopRef
    • CFRunLoopRef实际类型是__CFRunLoop
    • 主要成员结构有下面几个
        typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
        struct __CFRunLoop {
            pthread_t _pthread; //代表线程,表明线程和RunLoop的关系是一一对应关系
            CFMutableSetRef _commonModes; //NSMutableSet<NSString *>字符串类型的集合,存储的是字符串,记录所有标记为common的mode 
            CFMutableSetRef _commonModeItems;//存储所有commonMode的item(source、timer、observer)
            CFRunLoopModeRef _currentMode;//当前运行的mode
            CFMutableSetRef _modes;//存储的是CFRunLoopModeRef,
        };
        _modes中存放的是CFRunLoopModeRef类型数据, 其中就有_currentMode,
        只不过_currentMode是当前使用的mode
        
        CommonMode不是实际存在的一种Mode,把一些Mode打上Common标记
        是同步Source/Timer/Observer到多个Mode中的一种技术方案
        
        CommonModes:一个 Mode 可以将自己标记为 Common 属性(通过将其 ModeName 添加到 RunLoop 的 commonModes 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems里的 Source/Observer/Timer 同步到具有 Common 标记的所有 Mode 里。
        
        _commonModeItems代表着能在Common模式下工作的单元
    
  • CFRunLoopModeRef(RunLoop运行模式)
    • CFRunLoopModeRef的结构如下
    • 主要成员结构有下面几个
        typedef struct __CFRunLoopMode *CFRunLoopModeRef;
        struct __CFRunLoopMode {
            CFStringRef _name; //模式的名称
            CFMutableSetRef _sources0;
            CFMutableSetRef _sources1;
            CFMutableArrayRef _observers;
            CFMutableArrayRef _timers;
        };
        其中的_sources0和_sources1是CFRunLoopSourceRef类型数据
        其中的_observers是CFRunLoopObserverRef类型数据
        其中的_timers是CFRunLoopTimerRef类型数据
    
    • 说明
        1.CFRunLoopModeRef代表RunLoop的运行模式
        2.一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
        3.RunLoop启动时只能选择其中一个Mode,作为currentMode
        4.如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入
           不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
        5.如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
    
    • CFRunLoopModeRef有5种运行模式,常见的是前两种
        1.kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
        2.UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
        3.UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode
        4.GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
        5.kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode 
    
  • CFRunLoopSourceRef
    • source0 需要手动唤醒线程
    • source1 具备唤醒线程的能力
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

他们之间的关系(一对多模式)

监听RunLoop状态

  • 我们可以通过给RunLoop添加Observer的方式监听RunLoop的状态, 使用下面这个函数
/**
     给RunLoop添加Observer
    
     @param rl 目标RunLoop
     @param observer 需要添加的Observer
     @param mode 监听状态
     */
    void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);

  • 可以通过下面的函数创建Observer
 /**
     创建Observer
    
     @param allocator 分配器
     @param activities 需要监听的状态
     @param repeats 是否重复监听
     @param order 顺序
     @param callout 回调函数
     @param context 附加对象
     @return 创建好的Observer
     */
    CF_EXPORT CFRunLoopObserverRef CFRunLoopObserverCreate(CFAllocatorRef allocator, CFOptionFlags activities, Boolean repeats, CFIndex order, CFRunLoopObserverCallBack callout, CFRunLoopObserverContext *context);

  • RunLoop的状态有下面几种
    /* Run Loop Observer Activities */
    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
        kCFRunLoopEntry = (1UL << 0),//即将进入RunLoop
        kCFRunLoopBeforeTimers = (1UL << 1),  //即将处理Timer
        kCFRunLoopBeforeSources = (1UL << 2), //即将处理Source
        kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
        kCFRunLoopAfterWaiting = (1UL << 6),  //即将从休眠中唤醒
        kCFRunLoopExit = (1UL << 7),          //即将进入RunLoop
        kCFRunLoopAllActivities = 0x0FFFFFFFU 
    };
  • 监听的应用
void observeRunLoopActicities(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;
    }
}

- (void)viewDidLoad {
    // 创建Observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
    // 添加Observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 释放
    CFRelease(observer);
}

RunLoop运行逻辑(转自:小码哥iOS学习笔记第十七天: Runloop运行逻辑

  • 在RunLoop的CFRunLoopModeRef中, 主要有以下几个成员变量
Source0
触摸事件处理
performSelector:onThread:

Source1
基于Port的线程间通信
系统事件捕捉

Timers
NSTimer
performSelector:withObject:afterDelay:

Observers
用于监听RunLoop的状态
UI刷新(BeforeWaiting)
Autorelease pool(BeforeWaiting)
  • RunLoop运行逻辑
01、通知Observers:进入Loop
02、通知Observers:即将处理Timers
03、通知Observers:即将处理Sources
04、处理Blocks
05、处理Source0(可能会再次处理Blocks)
06、如果存在Source1,就跳转到第807、通知Observers:开始休眠(等待消息唤醒)
08、通知Observers:结束休眠(被某个消息唤醒)
    01> 处理Timer
    02> 处理GCD Async To Main Queue
    03> 处理Source1
09、处理Blocks
10、根据前面的执行结果,决定如何操作
    01> 回到第0202> 退出Loop
11、通知Observers:退出Loop
  • 图解RunLoop运行逻辑

  • RunLoop的休眠原理

RunLoop线程保活(链接:小码哥iOS学习笔记第十七天: Runloop线程保活