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开发的学习路线起点不在这里,这些文件只会给你学习带来困扰. 打开一个项目,我们 ...
随机推荐
- 2017.7.18 linux下ELK环境搭建
参考来自:Linux日志分析ELK环境搭建 另一篇博文:2017.7.18 windows下ELK环境搭建 0 版本说明 因为ELK从5.0开始只支持jdk 1.8,但是项目中使用的是JDK 1 ...
- EffectiveJava(2)应对多个构造函数应当使用构建器
** 应对多个构造函数应当使用构建器 ** 静态工厂和构造器都不能很好的扩展到大量的可选参数,遇到大量参数有大量可选域时,只能重复生成可选参数递增的构造方法,这种构造模式叫做重叠构造器模式 javaB ...
- 构建Spring Boot程序有用的文章
构建Spring Boot程序有用的文章: http://www.jb51.net/article/111546.htm
- wmi在渗透测试中的运用
Abusing WMI to Build a Persistent, Asynchronous, and Fileless Backdoor 滥用 WMI 打造一个永久.异步.无文件后门 http:/ ...
- 作者:wallimn
经过这几天对DHTMLXTree的折腾总算是有点眉目了.领导催得紧.组长紧的催. 唉,把握这次机会来好好总结一下DHTMLXTree. 还是老套路.首先来简单了解一下DHTMLXTree. DHTML ...
- Oracle ODBC无Oracle连接驱动
.下载odbc驱动 需要下载两个东西 instantclient.zip instantclient.zip 下载地址:http:.html 解压放到同一个目录(无冲突) .将oracle数据库所在电 ...
- JavaScript 判断浏览器及版本
/* 智能机浏览器版本信息: alert("语言版本: "+browser.language); alert(" 是否为移动终端: "+browser.vers ...
- 合并apk和odex
Android的ROM中有很多odex文件,相对于APK中的dex文件而言这个odex有什么作用呢? 如果你仔细观察会发现文件名时一一对应的,同时那些对应的apk文件中没有dex文件.这样做可以使其厂 ...
- curl测试Docker容器连通性
通过curl来测试docker对外访问是否正常,这里测试Docker tomcat容器访问: [root@mysqlserver ~]# curl http://172.17.0.8:8080 < ...
- spark入门(helloworld插件)
1 http://www.cnblogs.com/openfire/archive/2013/04/26/3044722.html 2 在bulid文件夹下,(注意为主目录不是插件下的bulid.xm ...