NSTimer运行机制和线程问题
A、首先要理解NSTimer运行机制和Runloop之间的关系:
1.IOS的Run Loops机制
Run Loops是线程的基础部份,任何线程,包括主结程,都包含了一个run loop对象,Cocoa和CoreFoundation层都有对应的Run Loop实现。
Run loop 对线程的作用,就是用来控制当有事件需要处理的时候,让线程快速响应,而当没有工作的时候,线程改为休息。
本质上Run Loop是一个While死循环。不停地监听事件以及处理事件。我们可以自己写一个While循环来做到这点,但是苹果的封装显然会更好。比如可以有不同的运行模式、不同的接收源和定时源,不工作的时候休息等。
在一个应用的主函数中:
int main(int argc, char * argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
UIApplicationMain()函数就会启动一个主线程,并且自动为它设置一个Run Loop对象,但对除此以外,其它的线程需要明确去设置和启动。
2.适用场景
只有创建一个附属线程的时候,才需要明确去运行一个Run Loop.而且是在确定需要的时候才去设置和启动它,否则就必要了。
例如,开一个线程去执行一个明确的长时间的任务,就没有必要。起用Run Loop主要还是为了跟创建的线程可以有更多的交互。
而使用Run Loop一个明显的好处就是:节约计算资源,同时也就节约用电了。因为在没有触发的时候线程是处于休眠状态的,不会消耗CPU资源。
3.结构
一个Run Loop可以接收的事件类型有两种:一种是输入(Input Source);一种是时间资源(time source).前者异步传递事件的,通常消息是来自其它线程或应用发送的;而后者是同步事件的,比如:定时计划,或者定时重复的工作。
4.观察者
Run Loop对象的循环过程中可以添加观察者对象。整个Run Loop 在运行过程中发生的事件具体如下:
- 通知观察者Run Loop开始了;
- 通知观察者任何预设好的时间已经触发;
- 通知观察者任何接入端口的输入源准备被触发;
- 触发任何非端口方式接入的输入源;
- 如果有任何一个基于端口方式的输入淅准备被触发,立即运行被时间,并且跳到9步;
- 通知观察者线程准备休息;
- 让线程休息,直到以下任何一个事件发生:
- 任何一个基于端口的输入淅有事件发生;
- 任何一个计时器触发;
- 该Run Loop设置的过期时间过时了;
- 该Run Loop被明确地唤醒;
- 通知观察者线程被唤醒;
- 运行待触发的事件:
- 如果一个用户自定义的计时器被触发,运行该计时器的事件,并重新运行Run Loop.跳转到第2步;
- 如果一个输入源被触发,传递该事件;
- 如果该Run Loop被明确唤醒并且没有超时,重新运行Run Loop循环。跳转到第2步;
- 通知观察者该Run Loop循环已经退出。
输入源(Input Source)
输入源主要有三种:
- 基于端口的输入源
- 自定义输入源
- selector源
其中selector源就是常用的“performSelector...”方法。
定时源
定时源adpujiu是常用的NSTimer,定时器类;基机制也是基于Run Loop运行的,只是在指定的间隔时间发送消息给需要处理的回调方法。
两种方法:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:4.0 targer:self selector:@selector(fireTimer:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
以及
[NSTimer scheduledTimerWithTimerInterval:
terget:self
selector:@selector(fireTimer:)
userInfo:nil
repeats:YES];
不过需要注意的是,如果Run Loop没有监视定时器相关模式,那么定时器将不会运行。
如果定时器开始时,Run Loop正在处理前面的事件,那么它会等Run Loop处理完了才开始。如果Run Loop不再运行,那么定时器也永远不再启动了。
使用方式
启动方式
- 无条件启动;
- 设置超时时间启动,如RunUntilDate方法;
- 指定某种模式下启动,如RunMode:beforDate:方法;
能出方法
- 启动时设定好设定超时时间;
- 显式的停止Run Loop(调用CFRunLoopStop函数);
启动模式
可用模式有 5 种,一般常用的都是default;
- default
- connection
- modal
- event tracking
- common modes
例子
在新线程中,注册一个Run Loop的观察者,监听每次循环过程的所有事件;同时启动一个定时器;
从日记中可以看到整个Run Loop 的过程,包括启动和结束、每次定时器唤醒时前后的事件、没有任何任务进入休眠的状态。
由此可以看出,Run Loop能更加精细地跟整个线程的运行过程交互。
- (void)viewDidLoad {
[super viewDidLoad];
// 在新线程中运行:
[self performSelectorInBackground:@selector(testRunLoop) withObject:ni;];
}
- (void)testRunloop {
// 获取当前线程的Run Loop
NSRunLoop *myRunLoop = [NSRunLoop currentRunLoop];
// 创建一个Run Loop 观察者对象;观察事件为每次循环的所有活动;
CFRunLoopObserverContext context = {, (__bridge void *)(self), NULL, NULL, NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities, YES, , &myRunLoopObserver, &context);
if (observer)
{
// 将Cocoa的NSRunLoop类型转换成Core Foundation的CFRunLoopRef类型
CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];
// 添加观察才对象到该Run Loop 上
CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
}
// 创建定时器,每0.1秒触发
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self
selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
// 重复启动Run Loop 5次
NSInteger loopCount = ;
do {
//启动 Run Loop 开始循环,直到指定的时间才结束,这里就是持续1秒;
//当调用RunUnitDate方法时,观察者检测到循环已经启动,开始根据循环的各个阶段的事件,调用上面注册的myRunLoopObserver回调函数。
[myRunLoop runUntiDate:[NSDate dateWithTimIntervalSinceNow:]];
// 运行完之后,会再一次调用回调函数,状态是KFRunLoopExit,表示循环结束。
loopCount--;
} while(loopCount);
NSlog(@"The End.");
}
- (void)doFireTimer:(NSTimer *)timer
{
NSLog(@"fire timer");
}
// Run loop观察者的回调函数:
void myRunLoopObserver(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"run loop entry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"run loop before timers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"run loop before sources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"run loop before waiting");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"run loop after waiting");
break;
case kCFRunLoopExit:
NSLog(@"run loop exit");
break;
default:
break;
}
}
代码
B、理解run loop后,才能彻底理解NSTimer的实现原理,也就是说NSTimer实际上依赖run loop实现的。
先看看NSTimer的两个常用方法:
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo; //生成timer但不执行 + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo; //生成timer并且纳入当前线程的run loop来执行
NSRunLoop与timer有关方法为:
- (void)addTimer:(NSTimer *)timer forMode:(NSString *)mode; //在run loop上注册timer
主线程已经有run loop,所以NSTimer一般在主线程上运行都不必再调用addTimer:。但在非主线程上运行必须配置run loop,该线程的main方法示例代码如下:
- (void)main
{
NSTimer *myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timer:) userInfo:nil repeats:YES];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:myTimer forMode:NSDefaultRunLoopMode]; //实际上这步是不需要,scheduledTimerWithTimeInterval已经纳入当前线程运行。如果使用timerWithTimeInterval则需要
while (condition)
[runLoop run];
}
实际上这个线程无法退出,因为有timer事件需要处理,[runLoop run]会一直无法返回。解决办法就是设置一个截止时间:
[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10.0]]; //每隔10秒检查下线程循环条件,当然时间值可以根据实际情况来定。
我们通常在主线程中使用NSTimer,有个实际遇到的问题需要注意。当滑动界面时,系统为了更好地处理UI事件和滚动显示,主线程runloop会暂时停止处理一些其它事件,这时主线程中运行的NSTimer就会被暂停。解决办法就是改变NSTimer运行的mode(mode可以看成事件类型),不使用缺省的NSDefaultRunLoopMode,而是改用NSRunLoopCommonModes,这样主线程就会继续处理NSTimer事件了。具体代码如下:
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timer:) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
大家可以参看博文http://bluevt.org/?p=209,加深理解NSTimer和NSRunLoop的关系。
以前博文中提到延迟调用的方法,其实就是在当前线程的run loop上注册timer来实现定时运行的。所以如果是在非主线程上使用,一定要有一个run loop。
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes; - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
NSTimer运行机制和线程问题的更多相关文章
- js运行机制(线程)
浏览器线程 js运作在浏览器中,是单线程的,即js代码始终在一个线程上执行,这个线程称为js引擎线程. 浏览器是多线程的,除了js引擎线程,它还有: UI渲染线程 浏览器事件触发线程 http请求线 ...
- [转]JavaScript线程运行机制
从开始接触js时,我们便知道js是单线程的.单线程,异步,同步,互调,阻塞等.在实际写js的时候,我们都会用到ajax,不管是原生的实现,还是借助jQuery等工具库实现,我们都知道,ajax可以实现 ...
- 深入理解JavaScript运行机制
深入理解JavaScript运行机制 前言 本文是写作在给团队新人培训之际,所以其实本文的受众是对JavaScript的运行机制不了解或了解起来有困难的小伙伴.也就是说,其实真正的原理和本文阐述的并不 ...
- Windows程序内部运行机制 转自http://www.cnblogs.com/zhili/p/WinMain.html
一.引言 要想熟练掌握Windows应用程序的开发,首先需要理解Windows平台下程序运行的内部机制,然而在.NET平台下,创建一个Windows桌面程序,只需要简单地选择Windows窗体应用程序 ...
- 从setTimeout谈JavaScript运行机制
从setTimeout说起 众所周知,JavaScript是单线程的编程,什么是单线程,就是说同一时间JavaScript只能执行一段代码,如果这段代码要执行很长时间,那么之后的代码只能尽情地等待它执 ...
- 简述JavaScript的运行机制
想要理解JavaScript的运行机制,需要分别深刻理解以下几个点: · JavaScript的单线程机制 · 任务队列(同步任务和异步任务) · 事件和回调函数 · 定时器 · Event Loop ...
- 通过案例对 spark streaming 透彻理解三板斧之三:spark streaming运行机制与架构
本期内容: 1. Spark Streaming Job架构与运行机制 2. Spark Streaming 容错架构与运行机制 事实上时间是不存在的,是由人的感官系统感觉时间的存在而已,是一种虚幻的 ...
- 深入浅出话VC++(1)——Windows程序内部运行机制
一.引言 要想熟练掌握Windows应用程序的开发,首先需要理解Windows平台下程序运行的内部机制,然而在.NET平台下,创建一个Windows桌面程序,只需要简单地选择Windows窗体应用程序 ...
- JS的运行机制
代码块: JS中的代码块是指由<script>标签分割的代码段.JS是按照代码块来进行编译和执行的,代码块间相互独立(即就算代码块1出错,但不影响代码块2的加载和执行),但变量和方法共享. ...
随机推荐
- 捉虫记2:windows程序句柄泄露的上下文环境
作为程序员,开发程序是基本功,而调试程序也是必不可少的技能之一.软件在主体功能开发完成后会经历各个阶段的测试,才会被发布.在测试过程中,出现较多的可能就是内存泄漏,句柄泄漏,异常崩溃等属于非功能型的软 ...
- C#.Net 如何动态加载与卸载程序集(.dll或者.exe)0-------通过应用程序域AppDomain加载和卸载程序集
本博客中以“C#.Net 如何动态加载与卸载程序集(.dll或者.exe)”开头的都是引用莫问奴归处 微软装配车的大门似乎只为货物装载敞开大门,却将卸载工人拒之门外.车门的钥匙只有一把,若要获得还需要 ...
- 【HDOJ】3473 Minimum Sum
划分树解.主席树解MLE. /* 3473 */ #include <iostream> #include <sstream> #include <string> ...
- hdu4630No Pain No Game (多校3)(树状数组)
http://acm.hdu.edu.cn/showproblem.php?pid=4630 给的题解没看懂..搜解题报告看 了N久 终于在cui大神的指点下 搞明白咋回事了 将1-N中的每个数ai ...
- .NET下文本相似度算法余弦定理和SimHash浅析及应用
余弦相似性 原理:首先我们先把两段文本分词,列出来所有单词,其次我们计算每个词语的词频,最后把词语转换为向量,这样我们就只需要计算两个向量的相似程度. 我们简单表述如下 文本1:我/爱/北京/ ...
- Archlinux里面安装VMware Tools
用虚拟机学习linux确实很方便,但是和主机的文件共享是个大问题,VMWARE TOOLS可以很好的解决这个问题,但是在ARCH里却不能向大多数linux那样方便的安装,在查了很多帖子试了无数遍之后, ...
- apache开源项目--ZooKeeper
ZooKeeper是Hadoop的正式子项目,它是一个针对大型分布式系统的可靠协调系统,提供的功能包括:配置维护.名字服务.分布式同步.组服务等.ZooKeeper的目标就是封装好复杂易出错的关键服务 ...
- 【转】【cocos2d-x教程】如何使用WebSocket
介绍 WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术.在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快 ...
- Hibernate4.x之映射关系--一对一映射
Hibernate的1-1映射关系主要分为两类: 1.Hibernate基于外键映射的1对1关联关系 对于基于外键的1-1关联,其外键可以存放在任意一边,在需要存放外键一端,增加many-to-one ...
- 27、Service
1服务可以通过startservice的方法 开启.通过stopservice的方法 停止. 服务有一个特点: 只会一次onCreate()方法一旦被创建出来,以后oncreate() 就不会再被执行 ...