博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS RunLoop分析
阅读量:7104 次
发布时间:2019-06-28

本文共 4970 字,大约阅读时间需要 16 分钟。

RunLoop是一个对象,用于处理程序运行中出现的各种事件(触摸事件、定时器、Selector),从而保证程序的持续运行;特点是没有事件处理的时候,会让线程进入休眠模式,从而节约CPU资源,提好程序性能。

// defaultMode启动void CFRunLoopRun(void) {     int32_t result;    do {        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);        CHECK_FOR_FORK();    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);}复制代码

从上面代码中可以看出runloop本质上就是do...while的循环,通过阀值kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result控制代码的运行。

1.线程与runloop的关系

  1. 主线程默认开启线程RunLoop,获取方法CFRunLoopGetMain();
  2. 苹果不允许直接创建线程RunLoop,在子线程中可以使用CFRunLoopGetCurrent 获取子线程的RunLoop对象;
  3. 线程与RunLoop关系是一一对应的,其关系保存在一个全局的字典中,子线程的RunLoop的创建是第一次获取时,销毁是在线程结束时。

CFRunLoopGetCurrent 调用时,会先看一下字典里有没有存子线程相对用的RunLoop,如果有则直接返回RunLoop,如果没有则会创建一个,并将与之对应的子线程存入字典中。

主线程CFRunLoopRef相关源码:

// 创建字典 CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);// 创建主线程 根据传入的主线程创建主线程对应的RunLoop CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());// 保存主线程 将主线程-key和RunLoop-Value保存到字典中 CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);复制代码

子线程CFRunLoopRef相关源码:

// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRefstatic CFMutableDictionaryRef loopsDic;// 访问 loopsDic 时的锁static CFSpinLock_t loopsLock;// 获取一个 pthread 对应的 RunLoop。CFRunLoopRef _CFRunLoopGet(pthread_t thread) {    // 加锁    OSSpinLockLock(&loopsLock);    if (!loopsDic) {        // 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。        loopsDic = CFDictionaryCreateMutable();        CFRunLoopRef mainLoop = _CFRunLoopCreate();        CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);    }    // 直接从 Dictionary 里获取。    CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));    if (!loop) {        // 取不到时,创建一个        loop = _CFRunLoopCreate();        CFDictionarySetValue(loopsDic, thread, loop);        // 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。        _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);    }    OSSpinLockUnLock(&loopsLock);    return loop;}CFRunLoopRef CFRunLoopGetMain() {    return _CFRunLoopGet(pthread_main_thread_np());}CFRunLoopRef CFRunLoopGetCurrent() {    return _CFRunLoopGet(pthread_self());}复制代码

2. CFRunLoopMode类型

  1. kCFRunLoopDefaultMode: 默认mode, 主线程一般在这个mode下运行
  2. UITrackingRunLoopMode: scrollView追踪触摸滑动,保证界面滑动中不受其他mode影响
  3. UIInitializationRunLoopMode: App启动时进入的第一个mode,后面用不到
  4. GSEventReceiveRunLoopMode: 系统内部时间mode
  5. kCFRunLoopCommonModes: 占位mode,标记kCFRunLoopDefaultMode和UITrackingRunLoopMode,不是真正的mode

3. CFRunLoopMode和CFRunLoop关系

CFRunLoopMode源码:

struct __CFRunLoopMode {    CFStringRef _name;            // Mode Name, 例如 @"kCFRunLoopDefaultMode"    CFMutableSetRef _sources0;    // Set     CFMutableSetRef _sources1;    // Set     CFMutableArrayRef _observers; // Array     CFMutableArrayRef _timers;    // Array     ...};复制代码

CFRunLoop源码:

struct __CFRunLoop {    CFMutableSetRef _commonModes;     // Set     CFMutableSetRef _commonModeItems; // Set     CFRunLoopModeRef _currentMode;    // Current Runloop Mode    CFMutableSetRef _modes;           // Set     ...};复制代码

CFRunLoopSourceRef

  1. 是事件产生的地方。Source有两个版本:Source0 和 Source1。
  2. Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
  3. Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程.

CFRunLoopTimerRef

基于时间触发器,可以和NSTimer混用。包含一个时间长度和一个函数指针(用于回调)。将其加入RunLoop时,RunLoop会注册这个时间点。时间点到时,RunLoop会被唤醒执行回调函数。

CFRunLoopObserverRef

观察者(观察RunLoop的状态),每个Observer都包含一个函数指针(用于回调),当RunLoop的状态发生改变时,观察者可以通过回调接收这个变化。

观察的时间点:

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};复制代码

流程如图:

一个RunLoop包含若干mode,每个mode又包含observer\timer\source,调用RunLoop的主函数是,只能指定一种mode即CurrentMode。如果切换mode,只能先退出Loop,重新指定一个mode,目的是分隔不同组的observer\timer\source,让其不互相影响。

Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

CFRunLoop对外暴露的管理 Mode 接口:

CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);CFRunLoopRunInMode(CFStringRef modeName, ...);复制代码

Mode 暴露的管理 mode item 的接口:

CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);复制代码

转载地址:http://awuhl.baihongyu.com/

你可能感兴趣的文章
我的友情链接
查看>>
我的友情链接
查看>>
mac下PYTHON安装
查看>>
解决TwitterSDK出现Failed to get request token错误提示
查看>>
shell脚本从入门到复杂 其五(基本运算符)
查看>>
centos7系统rpm方式安装docker
查看>>
Hibernate+jsp实现分页
查看>>
cocos2d-x 小记
查看>>
20130629一个认识自己的日子
查看>>
nfs的优化
查看>>
Centos 6.2(x64)下编译安装MySQL-5.5.22
查看>>
2017-2-23-文章摘录
查看>>
RAID技术汇总
查看>>
关闭VMwareWorkStation的嘀嘀声音(启动或重启Linux虚拟机时
查看>>
ppp协议LCP数据包
查看>>
apache启动报错
查看>>
ERP为何在国内怪象横生?
查看>>
安卓(android)4.0以后播放flash问题
查看>>
机器学习的特征究竟是什么
查看>>
Lync Server 2013 _ 启用Lync账户进行基本功能测试
查看>>