RunLoop,翻译过来是运行环路。我们在创建命令行项目和创建ios项目时,发现命令行项目当最后一行代码执行完后项目就自动退出了,而ios项目确可以一直运行,知道用户手动点击退出按钮。这就是因为ios项目在main函数中自动创建了runLoop,从而可以使项目可以一直响应用户的操作。

int main(int argc, char * argv[]) {@autoreleasepool {//这行代码 会自动创建主线程的RunLoop      return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));}
}

  我们可以将这个过程我们可以简化成:

  

我们从这个过程可以看出RunLoop的基本作用
 保持程序的持续运行
 处理App中的各种事件(比如触摸事件、定时器事件等)
 节省CPU资源,提高程序性能:该做事时做事,该休息时休息
  ......

我们平时开发中,涉及到RunLoop的挺多的,比如说定时器、手势识别、网络请求等等,

  

一、RunLoop的结构

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

①Foundation:NSRunLoop,它是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。

②Core Foundation:CFRunLoopRef,它提供了纯 C 函数的 API,所有这些 API都是线程安全的。(CFRunLoopRef是开源的)

两者关系:

  所以我们获取RunLoop对象也有两种方法:

Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象

  因为CFRunLoopRef是开源的,所以我们可以通过它来看一下它的实现结构。来到CFRunLoop.c文件中,找到了RunLoop的结构体定义:

//已剔除非必要部分struct __CFRunLoop {pthread_t _pthread;CFMutableSetRef _commonModes;CFMutableSetRef _commonModeItems;CFRunLoopModeRef _currentMode;CFMutableSetRef _modes;
};

  这里的Set和数组类似,只不过数组是有序的,而set是无序的,都是用来存放数据的,所以 CFMutableSetRef可以理解成可变数组,也就是说在一个RunLoop对象中,存储着一个线程对象,三个可变数组,一个当前模式。那么CFRunLoopModeRef又是什么呢?

  我们找到了它的定义:  

typedef struct __CFRunLoopMode *CFRunLoopModeRef;
//剔除了其他无关属性
struct __CFRunLoopMode {CFStringRef _name;CFMutableSetRef _sources0;CFMutableSetRef _sources1;CFMutableArrayRef _observers;CFMutableArrayRef _timers;
};

  所以RunLoop的结构是这样的:

  _pthread就是RunLoop对应的线程,每条线程都有唯一的一个与之对应的RunLoop对象。

  _commonModeItems和_commonModes是用来存放某些特定模式和模式内事件的,接下来会讲到。

  _currentMode,RunLoop当前所处的模式,当前模式是从_modes里面选择的。

  _modes:RunLoop的运行模式,一共有五种,但是我们经常用的就两三种:

- kCFRunLoopDefaultMode, App的默认运行模式,通常主线程是在这个运行模式下运行
- UITrackingRunLoopMode, 跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)页面滚动式所处的模式
- kCFRunLoopCommonModes, 伪模式,不是一种真正的运行模式
- UIInitializationRunLoopMode:在刚启动App时第进入的第一个Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到

  我们上面提到的_commonModeItems和_commonModes就是存放kCFRunLoopCommonModes这种模式的数据的。CommonModes其实并不是一种真正的模式,而是指可以在标记为Common Modes的模式下运行的伪模式。 目前被标记为Common Modes的模式: kCFRunLoopDefaultMode,UITrackingRunLoopMode,简单来说目前kCFRunLoopCommonModes就是指kCFRunLoopDefaultMode+UITrackingRunLoopMode。比如,我们经常遇到在tableview添加定时器后,当tableview滚动后timer就不响应了。

  这是因为tableview滚动式处在UITrackingRunLoopMode模式下的,而定时器默认是处在kCFRunLoopDefaultMode下的,所以当模式切换后,RunLoop就无法响应之前模式的时间了,故而无法响应定时器时间。所以我们的方案是将定时器添加到RunLoop的kCFRunLoopCommonModes模式下,这样无论是否滑动tableview都可以响应定时器事件了。

  这里还需要注意的一点是:如果需要切换 Mode,只能退出Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

  接下来,我们再来看一下这个RunLoop中的模式指的是什么?有什么作用?

  我们前面通过源码,看到了CFRunLoopMode的结构,里面有sources0、sources1、timer、observers,其实这里面就存储着app要处理的种种事情,它们分别负责不同的工作。它们的分工是这样的:(个人认为sources0和sources1其实是一个整体,当事件发生时sources1先去获取这个时间,涉及不到端口或内核或其他线程的事情的话就交给sources0处理,其余的自己处理)

sources0:只包含了一个回调(函数指针),它并不能主动触发事件,比如点击事件等操作都是通过sources0处理的。

sources1:包含了一个 mach_port 和一个回调(函数指针),用于通过内核和其他线程相互发送消息,这种 Source 能主动唤醒 RunLoop 的线程。

timer:是基于时间的触发器,其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。

observers:是观察者,当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。

  RunLoop的状态有一下几种:

  需要注意的一点是:如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出

二、RunLoop与线程

  关于RunLoop与线程的关系,我们可以总结以下几点:

每条线程都有唯一的一个与之对应的RunLoop对象线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop,子线程没有开启RunLoop的话就跟命令行项目一样,任务执行完就会结束RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为valueRunLoop会在线程结束时销毁

  接下来,我们通过源码来验证:

  当我们获取线程的Runloop的时候,发现RunLoop没有获取到话,都会调用__CFRunLoopGet0, 并把线程作为参数传递

  

  继续,跳转至__CFRunLoopGet0,如下:

  发现,RunLoop与线程的关系是一对一的,并且用了个全局字典保存了起来,线程作为key,RunLoop作为value。

我们发现如果线程没有启用RunLoop后会执行完马上销毁:

添加RunLoop后,发现还是运行完就销毁:这是因为如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出

所以我们需要往Model中添加一个数据:

发现确实执行完后,线程阻塞了,一直没有被销毁,这是因为当runtime创建后,如果没有被事件唤醒后它就一直在休眠,cpu就不会继续处理事情,所以阻塞在这。

三、RunLoop的运行逻辑 

  我们在了解RunLoop的结构以及与线程的关系后,我们再来看一下RunLoop的运行流程:

  接下来,我们通过源码来看一下RunLoop是如何处理这些事件的?

关于入口的查找,我们可以现在touchesBegan:方法中打个断点,查看程序是怎么执行到这的:

//RunLoop入口
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT *///通知Observers 进入RunLoop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);//RunLoop的具体运行result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);//通知Observers 退出RunLoop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);return result;
}//RunLoop的具体运行
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {int32_t retVal = 0;do {//通知Observers 即将处理Timers
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);//通知Observers 即将处理Sources
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);//处理Block
        __CFRunLoopDoBlocks(rl, rlm);//处理Sources0if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {//处理Block
            __CFRunLoopDoBlocks(rl, rlm);}Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);// 如果当前是主线程的runloop,并且主线程有事情需要处理,则跳转至handle_msg处理,即跳过休眠  这条指令网上大部分说法是指判断Sources1中是否有事情处理,个人觉得这个说法不太对,这篇文章中有正面:资料if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {goto handle_msg;}//通知Observers 即将休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);//开始休眠
        __CFRunLoopSetSleeping(rl);//等待别的消息来唤醒当前线程  如果没有消息就会一直在这休眠 阻塞在这 cpu不工作  有消息的话则唤醒执行__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);//结束休眠
        __CFRunLoopUnsetSleeping(rl);//通知Observers 结束休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);//handle_msg
handle_msg:;if (被timer唤醒) {//处理Timers
           __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())}else if (被gcd唤醒) {//处理gcd
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);} else {//被sources1唤醒//处理Sources1__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)}//处理Block
        __CFRunLoopDoBlocks(rl, rlm);//处理返回值if (sourceHandledThisLoop && stopAfterHandle) {retVal = kCFRunLoopRunHandledSource;} else if (timeout_context->termTSR < mach_absolute_time()) {retVal = kCFRunLoopRunTimedOut;} else if (__CFRunLoopIsStopped(rl)) {__CFRunLoopUnsetStopped(rl);retVal = kCFRunLoopRunStopped;} else if (rlm->_stopped) {rlm->_stopped = false;retVal = kCFRunLoopRunStopped;} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {retVal = kCFRunLoopRunFinished;}} while (0 == retVal);return retVal;
}

  简化成流程图 则是:

  

四、RunLoop的应用

控制线程生命周期(线程保活),比较经典的就是AFNetworking案例;

解决NSTimer在滑动时停止工作的问题,这个是我们平时开发中遇到过的;

滚动视图流畅性优化

App卡顿监测

阻止App崩溃

相关参考资料:

RunLoop源码解析

RunLoop

转载于:https://www.cnblogs.com/gaoxiaoniu/p/10823349.html

RunLoop相关知识相关推荐

  1. JS作用域相关知识(#精)

    在学习<你不知道的JS>一书中,特将作用域相关知识在此分享一下: #说到作用域,就不得不提到LHS查询和RHS查询: 1)如果查询目的是对变量进行赋值,则使用LHS查询 2)如果查询目的是 ...

  2. 工业相机参数之帧率相关知识详解

    点击上方"小白学视觉",选择加"星标"或"置顶"重磅干货,第一时间送达 工业相机是机器视觉系统的重要组成部分之一,在机器视觉系统中有着非常重 ...

  3. shell的相关知识(变量、脚本定义)

    一.shell的相关知识: 1.对于shell编程语言大体分为:机器语言.汇编语言.高级语言 2.shell变量类型:事先确定数据的存储格式和长度 shell变量分为:字符型.数值型 数值型又分为:整 ...

  4. 视频压缩算法的相关知识

    视频压缩算法的相关知识 MPEG-1 MPEG 视频压缩编码后包括三种元素:I帧(I-frames).P帧(P-frames)和B帧(B-frames).在MPEG编码的过程中,部分视频帧序列压缩成为 ...

  5. linux 格式化 dvd,linux 服务器分区格式化相关知识 -mount

    关于linux 系统mount和mkfs 的相关知识: 使用mount 1)Mount的相关格式:mount [-t 文件类型][-o  选项] devicedir 详解: -t 文件类型,通常默认m ...

  6. WinForm开发,窗体显示和窗体传值相关知识总结

    以前对WinForm窗体显示和窗体间传值了解不是很清楚 最近做了一些WinForm开发,把用到的相关知识整理如下 A.WinForm中窗体显示显示窗体可以有以下2种方法: Form.ShowDialo ...

  7. js基础--数据类型检测的相关知识

    欢迎访问我的个人博客:www.xiaolongwu.cn 前言 最近工作有点忙,好几天都没更新技术博客了. 周末起床打开有道云笔记,发现自己的博客todolist里躺了一堆只有名字的文件. 话不多说, ...

  8. 转载:关于错排的相关知识

    转载:关于错排的相关知识 杭电2048相关知识充电 转自:错排公式 分类: 数论 关于程序2012-06-08 19:07 335人阅读 评论(0) 收藏 举报 n2 错排问题 错排问题 就是一种递推 ...

  9. VMware虚拟网络相关知识

    VMware虚拟网络相关知识 虚拟网桥         通过虚拟网桥把虚拟机的虚拟网卡连接到宿主机的物理网卡上.通过它可以将虚拟机连接到宿主机所在的外部网络.如果宿主机上不止是一个物理网卡时,采用定制 ...

  10. 有关线程的相关知识(下)

    转载请标明出处: http://blog.csdn.net/hai_qing_xu_kong/article/details/70339618 本文出自:[顾林海的博客] 前言 在上一篇文章<有 ...

最新文章

  1. 快上车!“正经”文章告诉你如何构建与使用分布式中间件平台实践
  2. 使用HttpHandler实现图片防盗链
  3. Mybatis逆向工程的pojo实现序列化接口的代码
  4. 前端人员如何在linux服务器上搭建npm私有库
  5. 《看聊天记录都学不会C语言?太菜了吧》(5)打了一把游戏我学会了一个编程知识?
  6. @vail 判断某字段在范围内_怎么判断一台二次元影像测量仪的可靠性?
  7. 报错信息:NoReverseMatch at / 'blog' is not a registered namespace
  8. 有趣的算法(七):3分钟看懂希尔排序(C语言实现)
  9. Java集合的常见面试题(全)
  10. 新唐N76E003与ST公司STM8S003F3芯片对比 史上最全的没有之一
  11. 信息化和信息系统-PMP
  12. 建材物资管理系统(软件定义)
  13. python keys方法_Robot Framework selenium操作键盘press keys方法详解(Python篇)
  14. windows进程详解
  15. Python+Vue计算机毕业设计BeatHouse伴奏交易平台z19pu(源码+程序+LW+部署)
  16. 简单的扫描枪模拟程序
  17. armbian启用wifi
  18. igh ethercat主站文档(中文翻译上)
  19. lucky前面加a还是an_luck、lucky、luckily的区别和用法-luckly-英语-徐似眉同学
  20. 企业进行营销型网站制作的制作方法与注意事项

热门文章

  1. Linux常用命令介绍(三)——基础操作命令
  2. CSS详解(一)——CSS基本原理
  3. bash下常用快捷键以及Linux内部帮助文档的使用
  4. Outlook 2016最佳实践视频课程上线
  5. 硬盘数据恢复的神器有哪些
  6. 深层网络搜索核心技术研讨
  7. Spring【DAO模块】就是这么简单
  8. QTP不识别树结构中的点击事件
  9. 蓝桥杯“基础练习: 十六进制转十进制
  10. 自定义http报头_http协议报头详解HTTP协议结构