何为RunLoop?RunLoop有哪些应用场景?
一、RunLoop的作用
一个应用开始运行以后放在那里,如果不对它进行任何操作,这个应用就像静止了一样,不会自发的有任何动作发生,但是如果我们点击界面上的一个按钮,这个时候就会有对应的按钮响应事件发生。给我们的感觉就像应用一直处于随时待命的状态,在没人操作的时候它一直在休息,在让它干活的时候,它就能立刻响应。其实,这就是run loop的功劳。
二、线程与runloop的关系
<1>线程任务的类型
线程的任务可以形象地分为:
(1)直线型:执行一段任务之后,就被释放掉了。
(2)环型:不断循环,直到通过某种方式将它终止。
<2>线程与run loop的关系
Run loop,正如其名,loop表示某种循环,和run放在一起就表示一直在运行着的循环。实际上,run loop和线程是紧密相连的,可以这样说run loop是为了线程而生,没有线程,它就没有存在的必要。Run loops是线程的基础架构部分,Cocoa和CoreFundation都提供了run loop对象方便配置和管理线程的run loop(以下都已Cocoa为例)。每个线程,包括程序的主线程(main thread)都有与之相应的run loop对象。
iOS 系统中,提供了两种RunLoop:NSRunLoop 和 CFRunLoopRef。
<1 CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
<2 NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。
<3 CFRunLoopRef 的代码是开源的。
其中:主线程中的runloop是默认启动的。
- int main(int argc, char *argv[])
- {
- @autoreleasepool {
- return UIApplicationMain(argc, argv, nil, NSStringFromClass([appDelegate class]));
- }
- }
重点是UIApplicationMain() 函数,这个方法会为main thread 设置一个NSRunLoop 对象。这样就能解释了为什么系统没有任务执行时进行死亡状态,有任务执行时又能进行响应。
三、RunLoop的应用场景
1.保持线程的存活,而不是线性的执行完任务就退出了
<1>不开启RunLoop的线程
在遇到一些耗时操作时,为了避免主线程阻塞导致界面卡顿,影响用户体验,往往我们会把这些耗时操作放在一个临时开辟的子线程中。操作完成了,子线程线性的执行了代码也就退出了,就像下面一样。
- -(void)notDidThread{
- NSLog(@"%@ -------开辟子线程",[NSThread currentThread]);
- MyThread *subThread = [[MyThread alloc]initWithTarget:self selector:@selector(subThreaddo) object:nil];
- subThread.name = @"subThread";
- [subThread start];
- }
- -(void)subThreaddo{
- NSLog(@"%@----执行子线程任务",[NSThread currentThread]);
- }
其中MyThread是一个继承自NSThread的子类,并重写了dealloc方法。
- -(void)dealloc
- {
- NSLog(@"%@线程被释放了", self.name);
- }
看一下打印结果:
- <NSThread: 0x600001a22880>{number = , name = main} -------开辟子线程
- <MyThread: 0x600001a42640>{number = , name = subThread}----执行子线程任务
- subThread线程被释放了
可以看到子线程subThread在任务执行结束后,已经被释放掉了。
<1>开启RunLoop的线程
(1)实验用self来持有子线程
同样也是上个代码,让self对子线程进行持有,再看输出结果。
- self.subThread = [[MyThread alloc]initWithTarget:self selector:@selector(subThreaddo) object:nil];
- self.subThread.name = @"subThread";
- [self.subThread start];
- <NSThread: 0x600002f9e900>{number = , name = main} -------开辟子线程
- <MyThread: 0x600002fc2c40>{number = , name = subThread}----执行子线程任务
在任务执行完成之后,子线程并没有被释放掉。那既然没有被释放掉,如果再去重新开启能行吗?
- self.subThread = [[MyThread alloc]initWithTarget:self selector:@selector(subThreaddo) object:nil];
- self.subThread.name = @"subThread";
- [self.subThread start];
- [self.subThread start];//重新开启一次
- <NSThread: 0x600002cb8000>{number = , name = main} -------开辟子线程
- <MyThread: 0x600002cd5ac0>{number = , name = subThread}----执行子线程任务
- *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[MyThread start]: attempt to start the thread again'
发现已经崩溃了。任务执行完毕后,thread虽然没有被释放掉,还是处于内存中,但是它处于死亡状态(当线程执行完毕后,都会进如到这种状态),所以如果重新开启会出现崩溃。苹果在线程死亡后不允许重新开启。
<2>初步尝试使用RunLoop
现在我们来初步了解下RunLoop如何使用,顺便做个小测试。
- - (void)viewDidLoad {
- [super viewDidLoad];
- NSLog(@"%@----开辟子线程",[NSThread currentThread]);
- NSThread *subThread = [[MyThread alloc] initWithTarget:self selector:@selector(subThreadTodo) object:nil];
- subThread.name = @"subThread";
- [subThread start];
- }
- - (void)subThreadTodo
- {
- NSLog(@"%@----开始执行子线程任务",[NSThread currentThread]);
- //获取当前子线程的RunLoop
- NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
- //下面这一行必须加,否则RunLoop无法正常启用。我们暂时先不管这一行的意思,稍后再讲。
- [runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
- //让RunLoop跑起来
- [runLoop run];
- NSLog(@"%@----执行子线程任务结束",[NSThread currentThread]);
查看输出结果:
- <NSThread: 0x600002621400>{number = , name = main} -------开辟子线程
- <MyThread: 0x600002677ec0>{number = , name = subThread}----执行子线程任务
这里没有对线程进行引用,也没有让线程内部的任务进行显式的循环。为什么子线程的里面的任务没有执行到输出任务结束这一步,为什么子线程没有销毁?就是因为[runLoop run];这一行的存在。
RunLoop本质就是个Event Loop的do while循环,所以运行到这一行以后子线程就一直在进行接受消息->等待->处理的循环。所以不会运行[runLoop run];之后的代码(这点需要注意,在使用RunLoop的时候如果要进行一些数据处理之类的要放在这个函数之前否则写的代码不会被执行),也就不会因为任务结束导致线程死亡进而销毁。
<3>如何创建RunLoop?
苹果不允许直接创建 RunLoop,它只提供了四个自动获取的函数
- [NSRunLoop currentRunLoop];//获取当前线程的RunLoop
- [NSRunLoop mainRunLoop];//获取主线程的RunLoop
- CFRunLoopGetMain();
- CFRunLoopGetCurrent();
函数内部的逻辑大概是下面这样:
- /// 全局的Dictionary,key 是 线程, value 是 CFRunLoopRef
- static 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());
- }
注:这并不是源码,而是大神为了方便我们理解,对源码进行了一些可读性优化后的结果。
1、线程默认不开启RunLoop,为什么我们的App或者说主线程却可以一直运行而不会结束?
主线程是唯一一个例外,当App启动以后主线程会自动开启一个RunLoop来保证主线程的存活并处理各种事件。而且从上面的源代码来看,任意一个子线程的RunLoop都会保证主线程的RunLoop的存在。
2、RunLoop能正常运行的条件是什么?
看到刚才代码中注释说暂时不管的代码,第一次接触肯定会想[runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];这一句是什么意思?为什么必须加这一句RunLoop才能正常运行?
- - (void)viewDidLoad {
- [super viewDidLoad];
- NSLog(@"%@----开辟子线程",[NSThread currentThread]);
- NSThread *subThread = [[MyThread alloc] initWithTarget:self selector:@selector(subThreadTodo) object:nil];
- subThread.name = @"subThread";
- [subThread start];
- }
- - (void)subThreadTodo
- {
- NSLog(@"%@----开始执行子线程任务",[NSThread currentThread]);
- //获取当前子线程的RunLoop
- NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
- //注释掉下面这行和不注释掉下面这行分别运行一次
- [runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
- NSLog(@"RunLoop:%@",runLoop);
- //让RunLoop跑起来
- [runLoop run];
- NSLog(@"%@----执行子线程任务结束",[NSThread currentThread]);
- }
注释掉得到的结果
不注释得到的结果
注释掉以后我们看似run了RunLoop但是最后线程还是结束了任务,然后销毁了。与没注释得到的结果比较,造成这一切的原因就在上面两张图片中标注部分的区别上。要解释这一部分就又要开始讲到让我们抓耳挠腮的概念部分,我们先来看一张眼熟到不行的RunLoop结构图。
一开始接触RunLoop我看到这张图的时候也是懵逼的,现在我们结合刚才的打印结果来理解。
图中RunLoop蓝色部分就对应我们打印结果中,整个RunLoop部分的打印结果
多个绿色部分共同被包含在RunLoop内就对应,打印结果中modes中同时包含多个Mode(这里可是看打印结果中标注出来的第一行往上再数两行。modes = ... count = 1。一个RunLoop可以包含多个Mode,每个Mode的Name不一样,只是在这个打印结果当中目前刚好Mode个数为1)
每一个绿色部分Mode整体就对应,打印结果中被标注出来的整体。
黄色部分Source对应标注部分source0+source1
黄色部分Observer对应标注部分observer部分
黄色部分Timer对应标注部分timers部分
<1 Mode
我对Mode的理解就是”行为模式“,就像我们说到上学这个行为模式,它就应该包含起床,出门,去学校,上课,午休等等。但是,如果上学这个行为模式什么都不包含,那么即使我们进行上学这个行为,我们也一直睡在床上什么都不会做。就像刚才注释掉addPort那一行代码得到的结果一样,RunLoop在kCFRunLoopDefaultMode下run了,但是因为该Mode下所有东西都为null(不包含任何内容),所以RunLoop什么都没做又退出来了,然后线程就结束任务最后销毁。之所以要有Mode的存在是为了让RunLoop在不同的”行为模式“之下执行不同的”动作“互不影响。比如执行上学这个行为模式就不能进行娱乐这个行为模式下的游戏这个动作。RunLoop同一时间只能运行在一种Mode下,当前运行的这个Mode叫currentMode。(这里也许比较抽象,在下面timer部分会有实例结合实例分析。)
一般我们常用的Mode有三种
- .kCFRunLoopDefaultMode(CFRunLoop)/NSDefaultRunLoopMode(NSRunLoop)
- 默认模式,在RunLoop没有指定Mode的时候,默认就跑在DefaultMode下。一般情况下App都是运行在这个mode下的
- .(CFStringRef)UITrackingRunLoopMode(CFRunLoop)/UITrackingRunLoopMode(NSRunLoop)
- 一般作用于ScrollView滚动的时候的模式,保证滑动的时候不受其他事件影响。
- .kCFRunLoopCommonModes(CFRunLoop)/NSRunLoopCommonModes(NSRunLoop)
- 这个并不是某种具体的Mode,而是一种模式组合,在主线程中默认包含了NSDefaultRunLoopMode和 UITrackingRunLoopMode。子线程中只包含NSDefaultRunLoopMode。
- 注意:
- ①在选择RunLoop的runMode时不可以填这种模式否则会导致RunLoop运行不成功。
- ②在添加事件源的时候填写这个模式就相当于向组合中所有包含的Mode中注册了这个事件源。
- ③你也可以通过调用CFRunLoopAddCommonMode()方法将自定义Mode放到 kCFRunLoopCommonModes组合。
<2 Source
source就是输入源事件,分为source0和source1这两种。
- .source0:诸如UIEvent(触摸,滑动等),performSelector这种需要手动触发的操作。
- .source1:处理系统内核的mach_msg事件(系统内部的端口事件)。诸如唤醒RunLoop或者让RunLoop进入休眠节省资源等。
- 一般来说日常开发中我们需要关注的是source0,source1只需要了解。
- 之所以说source0更重要是因为日常开发中,我们需要对常驻线程进行操作的事件大多都是source0,稍后的实验会讲到。
<3 Timer
Timer即为定时源事件。通俗来讲就是我们很熟悉的NSTimer,其实NSTimer定时器的触发正是基于RunLoop运行的,所以使用NSTimer之前必须注册到RunLoop,但是RunLoop为了节省资源并不会在非常准确的时间点调用定时器,如果一个任务执行时间较长,那么当错过一个时间点后只能等到下一个时间点执行,并不会延后执行(NSTimer提供了一个tolerance属性用于设置宽容度,如果确实想要使用NSTimer并且希望尽可能的准确,则可以设置此属性)。
<4 Observer
它相当于消息循环中的一个监听器,随时通知外部当前RunLoop的运行状态。NSRunLoop没有相关方法,只能通过CFRunLoop相关方法创建
- // 创建observer
- CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, , ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
- NSLog(@"----监听到RunLoop状态发生改变---%zd", activity);
- });
- // 添加观察者:监听RunLoop的状态
- CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
由于它与这一问的关系并不大所以暂时不做过多阐述,希望进一步了解Observer可以查看文末的文档或者RunLoop入门学习补充资料(3.Observer)。
重点:它不能作为让RunLoop正常运行的条件,只有Observer的RunLoop也是无法正常运行的。
何为RunLoop?RunLoop有哪些应用场景?的更多相关文章
- RunLoop 总结:RunLoop的应用场景(一)
参考资料 好的书籍都是值得反复看的,那好的文章,好的资料也值得我们反复看.我们在不同的阶段来相同的文章或资料或书籍都能有不同的收获,那它就是好文章,好书籍,好资料.关于iOS 中的RunLoop资料非 ...
- 【转】深入理解RunLoop
RunLoop 是 iOS 和 OS X 开发中非常基础的一个概念,这篇文章将从 CFRunLoop 的源码入手,介绍 RunLoop 的概念以及底层实现原理.之后会介绍一下在 iOS 中,苹果是如何 ...
- iOS开发 - 啰嗦讲解 Runloop
写在前面的 为什么要了解 RunLoop?如果你想成为一个高级iOS开发工程师,那这是你必须了解的东西,他能帮助你更好的理解底层实现的原理,可以利用它的特性做出一些高效又神奇的功能.RunLoop这个 ...
- 运行循环 - RunLoop
1.RunLoop简介 1.1 什么是RunLoop 简单来说就是:运行循环,可以理解成一个死循环,一直在运行. RunLoop实际上就是一个对象,这个对象用来处理程序运行过程中出现的各种事件(触摸. ...
- 再谈RunLoop
RunLoop 一 概述: 一句话解释RunLoop:运行任务的循环. 为什么要有RunLoop:解决交互式UI设计中的一个问题,如何快速响应用户输入,如何快速将程序运行结果输出到屏幕? 计算机是个笨 ...
- RunLoop总结:RunLoop基础知识
没有实际应用场景,很难理解一些抽象空洞的东西,所以前面几篇文章先介绍了RunLoop的几个使用场景. 另外AsyncDisplayKit中也有大量使用RunLoop的示例. 关于实际的使用RunLoo ...
- iOS Runloop理解
一.RunLoop的定义 当有持续的异步任务需求时,我们会创建一个独立的生命周期可控的线程.RunLoop就是控制线程生命周期并接收事件进行处理的机制. RunLoop是iOS事件响应与任务处理最核心 ...
- RunLoop 原理和核心机制
搞iOS之后一直没有深入研究过RunLoop,非常的惭愧.刚好前一阵子负责性能优化项目,需要利用RunLoop做性能优化和性能检测,趁着这个机会深入研究了RunLoop的原理和特性. RunLoop的 ...
- RunLoop 之初探
你好2019!一起努力呀! 1.什么是runloop runloop是通过内部维护的事件循环对事件/消息进行管理的一个对象. 事件循环(Event loop):通俗的解释:没有消息处理的时候,休眠以避 ...
随机推荐
- Muduo网络库源代码分析(六)TcpConnection 的生存期管理
TcpConnection是使用shared_ptr来管理的类,由于它的生命周期模糊.TcpConnection表示已经建立或正在建立的连接.建立连接后,用户仅仅须要在上层类如TcpServer中设置 ...
- 抒发一下这些天用django做web项目的一些体会
最近接触了一段时间的python,觉得python写脚本还是挺方便的,做一个简单的桌面应用也很nice,但是随着深入,对python做功能复杂的web项目我彻底死心了,每个环节都是一堆的坑,部署阶段 ...
- android 微信朋友分享,朋友圈分享
android 微信朋友分享,朋友圈分享 包名必须写成 com.weixin WXEntryActivity package com.weixin.wxapi; import android.app ...
- 微信公众平台开发:进阶篇(Web App开发入门)
本文转载至:http://blog.csdn.net/yual365/article/details/16820805 WebApp与Native App有何区别呢? Native App: 1.开 ...
- 信息属性列表关键字 info.plist
本文转载至 http://blog.csdn.net/zaitianaoxiang/article/details/6650491 本附录说明了那些可以在束和包的属性列表文件中定义的关键字. 束关键 ...
- [Spring Data MongoDB]学习笔记--牛逼的MongoTemplate
MongoTemplate是数据库和代码之间的接口,对数据库的操作都在它里面. 注:MongoTemplate是线程安全的. MongoTemplate实现了interface MongoOperat ...
- 【转】在服务器上排除问题的头五分钟&常用命令
转自:https://blog.csdn.net/l821133235/article/details/80103106(在服务器上排除问题的头五分钟) 遇到服务器故障,问题出现的原因很少可以一下就想 ...
- Guava Joiner 拼接字符串
Joiner Guava 是Google 对Java的内置类型进行增强和扩展的工具. Joiner.on(", ").join(Iterator<> iter) Joi ...
- node.js, node-debug, node-inspector, npm 等等的使用问题解决
1.node-debug的error: /home/hzh/hzh/soft/softy/node-v6.10.0-linux-x64/lib/node_modules/node-inspector/ ...
- 升级到tomcat8遇到The method getDispatcherType() is undefined for the type HttpServletRequest
今天升级到tomcat8,发现原来的项目不能运行了,遇到下面的错误:The method getDispatcherType() is undefined for the type HttpServl ...