概念

RunLoop 就像她的名字一样,就是跑环,就是一个死循环。是一个可以随时休眠,随时唤醒的死循环。

那么一个手机App为什么会一直运行?而且在接受到用户点击的时候,会做出反应?这些都离不开RunLoop。

iOS App启动的时候,就会自动启动一个RunLoop。一直在循环监听着用户的各种操作,并作出反应。每个线程都有一个RunLoop,但是,只有主线程的RunLoop是默认开启的。可以这样理解:

1. RunLoop 是iOS消息机制的处理模式

NSRunLoop的主要作用:控制NSrunLoop里面的线程的执行和休眠,在有事做的时候,使当前的RunLoop控制线程工作,没事做的时候让当前的NS RunLoop控制线程休息。

2.NSRunLoop就是一只在循环检测,从线程start到线程的end,检测inputSource(点击,双击等操作)同步事件,检测timeSource(计时器)同步操作。检测到输入源会执行处理函数,首先会产生通知,corefunction向线程添加runloop observers来监听事件,意在监听事件发生时来做处理。

iOS main函数中的RunLoop

int main(int argc, char * argv[]) {
@autoreleasepool {
//一旦程序启动会开启一个RunLoop 一直循环监听用户的点击事件 触摸事件 定时器事件等 并且一直不会返回。
      保证程序一直运行,直到程序结束。这个默认的RunLoop就是跟主线程相关的。
int rs = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
return rs;
}
}

iOS中RunLoop对象。

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

一套是 Foundation -> NSRunLoop oc封装的

一套是 Core Foundtion ->CFRunLoopRef  C语言调用

但是 这两套API 是等价的,NSRunLoop就是基于CFRunLoopRef的一层OC包装。所以要研究NSRunLoop还是要研究CFRunLoopRef 。

RunLoop和线程的关系

1. 每条线程都有唯一的一个与之对应的RunLoop对象

2. 主线程的RunLoop已经自动创建好了,自线程的RunLoop需要自动创建

3. RunLoop在第一次获取时创建,在线程结束的时销毁。

RunLoop中的相关类

1.CFRunLoopRef

2.CFRunLoopModeRef RunLoop模式

3.CFRunLoopSourceRef 事件源 输入源

4.CFRunLoopTimerRef

5.CFRunLoopObserverRef

CFRunLoopModeRef 代表RunLoop的运行模式 系统提供了5中运行模式:

default模式:几乎包括所有输入源(除NSConnection) NSDefaultRunLoopMode模式

connection模式:处理NSConnection事件,属于系统内部,用户基本不用

UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。

UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。

NSRunLoopCommonModes: 包含了多种模式:default, modal, 和tracking modes。

一个RunLoop包含若干个Mode 每个Model又包含若干个Source/Timer/Observer

每次RunLoop启动时,只能指定其中一个Mode,这个mode被称作currentMode

如果需要切换Mode 只能tuichuLoop,再重新指定一个Mode进入。

CFRunLoopTimerRef

CFRunLoopTimerRef 就是基于时间的触发器

基本上说的就是NSTimer,他会收到RunLoop的mode的影响

GCD的定时器不会受到RunLoop的Mode的影响。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//这种方式启动的定时器 会自动加入到系统创建的RunLoop中
//[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES]; //使用这种方法创建的定时器 必须添加到定时器中 否则不会有作用
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
/*
如果是NSDefaultRunLoopMode 这样虽然定时器会工作,但是当拖动UITextView 的时候定时器就不在工作了,因为RunLoop有四种模式,它会在这四种模式中来回切换,当UITextView拖动时,RunLoop会进入UITrackingRunLoopMode模式,这时,就不再执行其他模式中的timer事件。当不再拖动时,会再次进入NSDefaultRunLoopMode模式,进行定时器事件。
如果换成 UITrackingRunLoopMode 模式,只有在UI拖动时,才会执行定时器事件。 那么如果你需要在UI拖动时不影响定时器事件的执行,我们可以使用NSRunLoopCommonModes 这其实不是一种模式,而是一种模式集合,包括UITrackingRunLoopMode 和 NSDefaultRunLoopMode。 NSTimer 计时不准确就是因为RunLoop在各种模式中自动切换进行的原因。GCD的计时是比较准确的
*/
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; }
- (void)show { NSLog(@"show -----");
}

GCD Timer

- (void)GCDTimer {
//第一步 创建队列
dispatch_queue_t queue = dispatch_get_global_queue(, );
//第二步 创建一个GCD定时器
/*
第一个参数 表明创建的是一个定时器
第四个参数 表示事件运行在哪个线程中
*/
dispatch_source_t sourceTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, ,, queue);
self.timer = sourceTimer;
//设置时间间隔
/*
第一个参数 定时器
第二个参事 定时器 开始时间
第三个参数 从现在开始间隔时间
第四个参数 精准度 GCD 的单位是纳秒
*/
dispatch_source_set_timer(sourceTimer, DISPATCH_TIME_NOW, 2.0 *NSEC_PER_SEC, *NSEC_PER_SEC);
//设置事件
dispatch_source_set_event_handler(sourceTimer, ^{
//要执行的任务
NSLog(@"GCDTimer");
});
//启动定时器
dispatch_resume(sourceTimer);
}

CFRunLoopSourceRef (事件源 输入源)

分类

Source0: 不是基于端口的 用户主动触发的事件。

Source1: 基于端口的 通过内核和其他线程相互发送消息

RunLoop 的消息类型

根据上图我们可以将消息分为二种类型,第一种类型又可以细分为三种,此三种共同点就是它们都是异步执行的

port ->source1

监听程序的Mach ports,Mach ports是一个比较底层的东西,可以简单的理解为:内核通过port这种方式将信息发送,而mach则监听内核发来的port信息,然后将其整理,打包发给runloop。

Customer:->source0

很明显,由开发人员自己发送。不仅仅是发送,过程的话相当复杂,苹果也提供了一个CFRunLoopSource来帮助处理。由于很少用到,可以简单说下核心,但是对帮助我们理解runloop却很有帮助:
1.定义输入源(数据结构)
2.将输入源添加到runloop,那么这样就有了接受者,即为R1
3.协调输入源的客户端(单独线程),专门监听消息,然后将消息打包成runloop能够处理的样式,即第一步定义的输入源。它类似Mach的功能
4.谁来发送消息的问题?上面的machport是由内核发送的。自定义的当然要我们自己发送了。。。首先必须是另一个线程来发送(当然如果只是测试的话可以和第三步在同一个线程),先发送消息给输入源,然后唤醒R1,因为R1一般处于休眠状态,然后R1根据输入源来做相应的处理

Selector Sources

NSObject类提供了很多方法供我们使用,这些方法是添加到runloop的,所以如果没有开启runloop的话,不会运行(不过有个坑,请看下面介绍)。

/// 主线程
performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:
/// 指定线程
performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:
/// 针对当前线程
performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:
/// 取消,在当前线程,和上面两个方法对应
cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:

下面提供的方法是在指定的线程运行aSelector,一般情况下aSelector会添加到指定线程的runloop。但,如果调用线程和指定线程为同一线程,且wait参数设为YES,那么aSelector会直接在指定线程运行,不再添加到runloop。

performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes: performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:

其实这也很好理解,假设这种情况也添加到指定线程的runloop,我们可以这样反向理解:1,当前线程runloop还没有开启,那么aSelector就不会被执行,然而你却一直在等待,造成线程卡死。2,当前线程runloop已经开启,那么调用performSelector这个方法的位置肯定是处于runloop的callout方法里面,在这里等待runloop再callout从而调用aSelector方法完成,显然也是死等待,线程卡死。。。

还有一些performSelector方法,是不会添加到runloop的,而是直接执行,可以按照上面的特殊情况进行理解。方法列举如下:

- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

看到这里,是否感觉有些乱???只要记住没有延迟或者等待的都不会添加到runloop,有延迟或者等待的还有排除上面提到的特殊情况

CFRunLoopObservers 观察者

首先它并不属于事件源(不会影响runloop的生命周期),它比较特殊,用于观察runloop自身的一些状态的,有以下几种:

1.进入RunLoop  kCFRunLoopEntry

2.RunLoop即将执行定时器  kCFRunLoopBeforeTimers

3.RunLoop即将执行输入源  kCFRunLoopBeforeSources

4.RunLoop即将休眠  kCFRunLoopBeforeWaiting

5.RunLoop被唤醒 在处理完唤醒它的事件之前  kCFRunLoopAfterWaiting

6.退出  kCFRunLoopExit

//给RunLoop添加一个监听者
- (void)observer {
//创建监听者
/**
param1: 给observer分配存储空间
param2: 需要监听的状态类型:kCFRunLoopAllActivities监听所有状态
param3: 是否每次都需要监听,如果NO则一次之后就被销毁,不再监听,类似定时器的是否重复
param4: 监听的优先级,一般传0
param5: 监听到的状态改变之后的回调
return: 观察对象
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, , ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"RunLoop进入");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即将处理timer");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即将处理input Sources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即将睡眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"从睡眠中唤醒,处理完唤醒源之前");
break;
case kCFRunLoopExit:
NSLog(@"退出");
break;
default:
break; }
}); /*
*第一个参数 RunLoop
*第二个参数 监听者
*第三个参数 要监听RunLoop在哪种运行模式下的状态
*/
[NSTimer scheduledTimerWithTimeInterval: target:self selector:@selector(doFireTimer) userInfo:nil repeats:NO];
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode); } - (void)doFireTimer {
NSLog(@"---fire---");
}

iOS 开发之RunLoop的更多相关文章

  1. 李洪强iOS开发之RunLoop的原理和核心机制

    李洪强iOS开发之RunLoop的原理和核心机制 搞iOS之后一直没有深入研究过RunLoop,非常的惭愧.刚好前一阵子负责性能优化项目,需要利用RunLoop做性能优化和性能检测,趁着这个机会深入研 ...

  2. iOS开发之Runloop(转)

    Objective-C之run loop详解 作者:wangzz 原文地址:http://blog.csdn.net/wzzvictory/article/details/9237973 转载请注明出 ...

  3. iOS 开发之 RunLoop 详解

    1)什么是 Runloop ? 1.字面上是运行循环,内部就是 do-while 循环,在这个循环内不断地处理各种任务. 2.一个线程对应一个 Runloop ,主线程的 RunLoop 默认是开启的 ...

  4. iOS开发之Socket通信实战--Request请求数据包编码模块

    实际上在iOS很多应用开发中,大部分用的网络通信都是http/https协议,除非有特殊的需求会用到Socket网络协议进行网络数 据传输,这时候在iOS客户端就需要很好的第三方CocoaAsyncS ...

  5. iOS开发之UISearchBar初探

    iOS开发之UISearchBar初探 UISearchBar也是iOS开发常用控件之一,点进去看看里面的属性barStyle.text.placeholder等等.但是这些属性显然不足矣满足我们的开 ...

  6. iOS开发之UIImage等比缩放

    iOS开发之UIImage等比缩放 评论功能真不错 评论开通后,果然有很多人吐槽.谢谢大家的支持和关爱,如果有做的不到的地方,还请海涵.毕竟我一个人的力量是有限的,我会尽自己最大的努力大家准备一些干货 ...

  7. iOS开发之 Xcode6 添加xib文件,去掉storyboard的hello world应用

    iOS开发之  Xcode6.1创建仅xib文件,无storyboard的hello world应用 由于Xcode6之后,默认创建storyboard而非xib文件,而作为初学,了解xib的加载原理 ...

  8. iOS开发之loadView、viewDidLoad及viewDidUnload的关系

    iOS开发之loadView.viewDidLoad及viewDidUnload的关系 iOS开发之loadView.viewDidLoad及viewDidUnload的关系    标题中所说的3个方 ...

  9. iOS开发之info.pist文件和.pch文件

    iOS开发之info.pist文件和.pch文件 如果你是iOS开发初学者,不用过多的关注项目中各个文件的作用.因为iOS开发的学习路线起点不在这里,这些文件只会给你学习带来困扰. 打开一个项目,我们 ...

随机推荐

  1. Node.js 本地Xhr取得Node.js服务端数据的例子

    本以为用XHR取Nodejs http出的一段文字很简单,因为xhr取值和nodejs http出文字都是好弄的,谁知一试不是这回事,中间有个关键步骤需要实现. nodejs http出文字显示在浏览 ...

  2. ANT使用 - 用for和foreach的方法遍历一个文件夹,查找到某个文件并删除

    转自:http://www.cnblogs.com/QAZLIU/p/3732329.html?utm_source=tuicool&utm_medium=referral build.xml ...

  3. vue笔记四

    十一.过渡与动画 1.使用限制Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加 entering/leaving 过渡条件渲染 (使用 v-if)条件展示 (使 ...

  4. TCP/IP 网络编程(五)

    优于 select 的 epoll (I/O 复用) select 速度慢的原因 调用select后针对全部文件描写叙述符的循环 每次调用函数时都须要向该函数传递监视对象信息 select并非把发生变 ...

  5. charles用法详解

    Charles是目前最强大的http调试工具,在界面和功能上远强于Fiddler,同时是全平台支持,堪称圣杯级工具,唯一的缺陷是这货是收费的,而且是要¥50美元大洋…当然网上是有破解版的,鄙视下自己, ...

  6. 【Lucene】Apache Lucene全文检索引擎架构之构建索引2

    上一篇博文中已经对全文检索有了一定的了解,这篇文章主要来总结一下全文检索的第一步:构建索引.其实上一篇博文中的示例程序已经对构建索引写了一段程序了,而且那个程序还是挺完善的.不过从知识点的完整性来考虑 ...

  7. Docker iptables failed: iptables -t nat -A DOCKER -p tcp

    Dokcer网络问题 因为操作或修该过iptables导致docker容器出现如下错误: [root@mysqlserver ~]# docker restart cvnavi-centos-tomc ...

  8. elk升级文档

    1.kibana等都统一版本了,5.4版本的kibana要5.4版本的elasticsearch 2.现有架构: logstash logstash读取日志-------->内网redis做队列 ...

  9. 通过小书匠编辑器让印象笔记和evernote支持markdown编辑

    a:focus { outline: thin dotted #333; outline: 5px auto -webkit-focus-ring-color; outline-offset: -2p ...

  10. vivado2013.4和modelsim联合仿真

    vivado2013.4和modelsim联合仿真                           Hello,Panda        最近在做Zynq的项目,曾经尝试使用ISE+PlanAhe ...