iOS 开发之RunLoop
概念
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的更多相关文章
- 李洪强iOS开发之RunLoop的原理和核心机制
李洪强iOS开发之RunLoop的原理和核心机制 搞iOS之后一直没有深入研究过RunLoop,非常的惭愧.刚好前一阵子负责性能优化项目,需要利用RunLoop做性能优化和性能检测,趁着这个机会深入研 ...
- iOS开发之Runloop(转)
Objective-C之run loop详解 作者:wangzz 原文地址:http://blog.csdn.net/wzzvictory/article/details/9237973 转载请注明出 ...
- iOS 开发之 RunLoop 详解
1)什么是 Runloop ? 1.字面上是运行循环,内部就是 do-while 循环,在这个循环内不断地处理各种任务. 2.一个线程对应一个 Runloop ,主线程的 RunLoop 默认是开启的 ...
- iOS开发之Socket通信实战--Request请求数据包编码模块
实际上在iOS很多应用开发中,大部分用的网络通信都是http/https协议,除非有特殊的需求会用到Socket网络协议进行网络数 据传输,这时候在iOS客户端就需要很好的第三方CocoaAsyncS ...
- iOS开发之UISearchBar初探
iOS开发之UISearchBar初探 UISearchBar也是iOS开发常用控件之一,点进去看看里面的属性barStyle.text.placeholder等等.但是这些属性显然不足矣满足我们的开 ...
- iOS开发之UIImage等比缩放
iOS开发之UIImage等比缩放 评论功能真不错 评论开通后,果然有很多人吐槽.谢谢大家的支持和关爱,如果有做的不到的地方,还请海涵.毕竟我一个人的力量是有限的,我会尽自己最大的努力大家准备一些干货 ...
- iOS开发之 Xcode6 添加xib文件,去掉storyboard的hello world应用
iOS开发之 Xcode6.1创建仅xib文件,无storyboard的hello world应用 由于Xcode6之后,默认创建storyboard而非xib文件,而作为初学,了解xib的加载原理 ...
- iOS开发之loadView、viewDidLoad及viewDidUnload的关系
iOS开发之loadView.viewDidLoad及viewDidUnload的关系 iOS开发之loadView.viewDidLoad及viewDidUnload的关系 标题中所说的3个方 ...
- iOS开发之info.pist文件和.pch文件
iOS开发之info.pist文件和.pch文件 如果你是iOS开发初学者,不用过多的关注项目中各个文件的作用.因为iOS开发的学习路线起点不在这里,这些文件只会给你学习带来困扰. 打开一个项目,我们 ...
随机推荐
- Java 学习之网络编程案例
网络编程案例 一,概念 1,网络编程不等于网站编程 2,编程只和传输层打交道,即TCP和UDP两个协议 二,案例 1,TCP实现点对点的聊天 Server端:两个输入流:读客户端和控制台,一个输出端: ...
- dom元素父子容器互相调用控制
在html中普通的父容器调用子容器中的方法十分简单 因为这两个容器的所有方法和属性都在同一个dom模型中 可以直接控制和使用 但是如果子容器中是一个iframe标签又是怎样的情况? iframe请求另 ...
- 2017.4.18 静态代码分析工具sonarqube+sonar-runner的安装配置及使用
配置成功后的代码分析页面: 可以看到对复杂度.语法使用.重复度等等都做了分析,具体到了每一个方法和每一句代码. 四种使用方式: sonarqube + sonar-runner sonarqube + ...
- C#与数据结构--图的遍历
http://www.cnblogs.com/abatei/archive/2008/06/06/1215114.html 8.2 图的存储结构 图的存储结构除了要存储图中各个顶点的本身的信息外,同时 ...
- [React] Use react-rewards to add microinteractions to React app to reward users for some actions
It's important that our users enjoy using our application or website. One way we can make it happen ...
- Linux组件封装(四)使用RAII技术实现MutexLock自动化解锁
我们不止一次写过这种代码: { mutex_.lock(); //XXX if(....) return; //XXX mutex_.unlock(); } 显然,这段代码中我们忘记了解锁.那么如何防 ...
- 在UC浏览器打开链接唤醒app,假设没有安装该app,则跳转到appstore下载该应用
在UC浏览器打开链接唤醒app,假设没有安装该app,则跳转到appstore下载该应用 须要在project中设置例如以下: 1.打开project中的myapp-Info.plist文件 2.打开 ...
- C语言之基本算法33—矩阵的基本运算
//矩阵基础 /* ================================================================== 题目:输入矩阵a,b,输出a,b,a的转置矩阵 ...
- HDU1789 Doing Homework again 【贪心】
Doing Homework again Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Oth ...
- UnrealEngine4 尝鲜
把官方事例的demo扩展成横版过关视角