NSRunLoop的进一步理解
iPhone应用开发中关于NSRunLoop的概述是本文要介绍的内容,NSRunLoop是一种更加高明的消息处理模式,他就高明在对消息处理过程进行了更好的抽象和封装,这样才能是的你不用处理一些很琐碎很低层次的具体消息的处理,在NSRunLoop中每一个消息就被打包在input source或者是timer source中了,来看详细内容。
1.什么是NSRunLoop
我们会经常看到这样的代码:
- - (IBAction)start:(id)sender
- {
- pageStillLoading = YES;
- [NSThread detachNewThreadSelector:@selector(loadPageInBackground:)toTarget:self withObject:nil];
- [progress setHidden:NO];
- while (pageStillLoading) {
- [NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
- }
- [progress setHidden:YES];
- }
复制代码
这段代码很神奇的,因为他会“暂停”代码运行,而且程序运行不会因为这里有一个while循环而受到影响。在[progress setHidden:NO]执行之后,整个函数想暂停了一样停在循环里面,等loadPageInBackground里面的操作都完成了以后才让[progress setHidden:YES]运行。这样做就显得简介,而且逻辑很清晰。如果你不这样做,你就需要在loadPageInBackground里面表示load完成的地方调用[progress setHidden:YES],显得代码不紧凑而且容易出错。
[iGoogle有话说:应用程序框架主线程已经封装了对NSRunLoop runMode:beforeDate:的调用;它和while循环构成了一个消息泵,不断获取和处理消息;可能大家会比较奇怪,既然主线程中已经封装好了对NSRunLoop的调用,为什么这里还可以再次调用,这个就是它与Windows消息循环的区别,它可以嵌套调用.当再次调用while+NSRunLoop时候程序并没有停止执行,它还在不停提取消息/处理消息.这一点与Symbian中Active Scheduler的嵌套调用达到同步作用原理是一样的.]
那么具体什么是NSRunLoop呢?其实NSRunLoop的本质是一个消息机制的处理模式。如果你对vc++编程有一定了解,在windows中,有一系列很重要的函数SendMessage,PostMessage,GetMessage,这些都是有关消息传递处理的API。
但是在你进入到Cocoa的编程世界里面,我不知道你是不是走的太快太匆忙而忽视了这个很重要的问题,Cocoa里面就没有提及到任何关于消息处理的API,开发者从来也没有自己去关心过消息的传递过程,好像一切都是那么自然,像大自然一样自然?在Cocoa里面你再也不用去自己定义WM_COMMAD_XXX这样的宏来标识某个消息,也不用在switch-case里面去对特定的消息做特别的处理。难道是Cocoa里面就没有了消息机制?答案是否定的,只是Apple在设计消息处理的时候采用了一个更加高明的模式,那就是RunLoop。
2. NSRunLoop工作原理
接下来看一下NSRunLoop具体的工作原理,首先是官方文档提供的说法,看图:
通过所有的“消息”都被添加到了NSRunLoop中去,而在这里这些消息并分为“input source”和“Timer source” 并在循环中检查是不是有事件需要发生,如果需要那么就调用相应的函数处理。为了更清晰的解释,我们来对比VC++和iOS消息处理过程。
VC++中在一切初始化都完成之后程序就开始这样一个循环了(代码是从户sir mfc程序设计课程的slides中截取):
- int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow){
- ...
- while (GetMessage(&msg, NULL, 0, 0)){
- if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- }
- }
可以看到在GetMessage之后就去分发处理消息了,而iOS中main函数中只是调用了UIApplicationMain,那么我们可以介意猜出UIApplicationMain在初始化完成之后就会进入这样一个情形:
- int UIApplicationMain(...){
- ...
- while(running){
- [NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
- }
- ...
- }
所以在UIApplicationMain中也是同样在不断处理runloop才是的程序没有退出。刚才的我说了NSRunLoop是一种更加高明的消息处理模式,他就高明在对消息处理过程进行了更好的抽象和封装,这样才能是的你不用处理一些很琐碎很低层次的具体消息的处理,在NSRunLoop中每一个消息就被打包在input source或者是timer source中了,当需要处理的时候就直接调用其中包含的相应对象的处理函数了。
所以对外部的开发人员来讲,你感受到的就是,把source/timer加入到runloop中,然后在适当的时候类似于[receiver action]这样的事情发生了。甚至很多时候,你都没有感受到整个过程前半部分,你只是感觉到了你的某个对象的某个函数调用了。
比如在UIView被触摸时会用touchesBegan/touchesMoved等等函数被调用,也许你会想,“该死的,我都不知道在那里被告知有触摸消息,这些处理函数就被调用了!?”所以,消息是有的,只是runloop已经帮你做了!为了证明我的观点,我截取了一张debug touchesBegan的call stack,如图:
利用NSRunLoop阻塞NSOperation线程
在使用NSOperationQueue简化多线程开发中介绍了多线程的开发,我这里主要介绍一下使用NSRunLoop阻塞线程。
主要使用在NStimer定时启用的任务或者异步获取数据的情况如socket获取网络数据,要阻塞线程,直到获取数据之后在释放线程。
下面是线程中没有使用NSRunLoop阻塞线程的代码和执行效果:
线程类:
#import <Foundation/Foundation.h>
@interface MyTask : NSOperation {
}
@end
#import "MyTask.h"
@implementation MyTask
-(void)main
{
NSLog(@"开始线程=%@",self);
[NSTimer timerWithTimeInterval:2 target:self selector:@selector(hiandeTime:) userInfo:nil repeats:NO];
}
-(void)hiandeTime:(id)sender
{
NSLog(@"执行了NSTimer");
}
-(void)dealloc
{
NSLog(@"delloc mytask=%@",self);
[super dealloc];
}
@end
线程添加到队列中:
- (void)viewDidLoad
{
[super viewDidLoad];
NSOperationQueue *queue=[[NSOperationQueue alloc] init];
MyTask *myTask=[[[MyTask alloc] init] autorelease];
[queue addOperation:myTask];
MyTask *myTask1=[[[MyTask alloc] init] autorelease];
[queue addOperation:myTask1];
MyTask *myTask2=[[[MyTask alloc] init] autorelease];
[queue addOperation:myTask2];
[queue release];
}
执行结果是:
2011-07-25 09:44:45.393 OperationDemo[20676:1803] 开始线程=<MyTask: 0x4b4dea0>
2011-07-25 09:44:45.393 OperationDemo[20676:5d03] 开始线程=<MyTask: 0x4b50db0>
2011-07-25 09:44:45.396 OperationDemo[20676:1803] 开始线程=<MyTask: 0x4b51070>
2011-07-25 09:44:45.404 OperationDemo[20676:6303] delloc mytask=<MyTask: 0x4b4dea0>
2011-07-25 09:44:45.404 OperationDemo[20676:5d03] delloc mytask=<MyTask: 0x4b50db0>
2011-07-25 09:44:45.405 OperationDemo[20676:6303] delloc mytask=<MyTask: 0x4b51070>
可以看到,根本没有执行NSTimer中的方法,线程就释放掉了,我们要执行
NSTimer中的方法,就要利用NSRunLoop阻塞线程。下面是修改后的代码:-(void)main
{
NSLog(@"开始线程=%@",self);
NSTimer *timer=[NSTimer timerWithTimeInterval:2 target:self selector:@selector(hiandeTime) userInfo:nil repeats:NO];
[timer fire];
while (!didDisconnect) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}执行结果如下:
2011-07-25 10:07:00.543 OperationDemo[21270:1803] 开始线程=<MyTask: 0x4d16380>
2011-07-25 10:07:00.543 OperationDemo[21270:5d03] 开始线程=<MyTask: 0x4d17790>
2011-07-25 10:07:00.550 OperationDemo[21270:6303] 开始线程=<MyTask: 0x4d17a50>
2011-07-25 10:07:00.550 OperationDemo[21270:1803] 执行了NSTimer
2011-07-25 10:07:00.551 OperationDemo[21270:5d03] 执行了NSTimer
2011-07-25 10:07:00.552 OperationDemo[21270:6303] 执行了NSTimer
2011-07-25 10:07:00.556 OperationDemo[21270:6503] delloc mytask=<MyTask: 0x4d16380>
2011-07-25 10:07:00.557 OperationDemo[21270:6303] delloc mytask=<MyTask: 0x4d17790>
2011-07-25 10:07:00.557 OperationDemo[21270:5d03] delloc mytask=<MyTask: 0x4d17a50>我们可以使用NSRunLoop进行线程阻塞。
使用runloop阻塞线程的正确写法
Runloop可以阻塞线程,等待其他线程执行后再执行。
比如:
@implementation ViewController{
BOOL end;
}
…
– (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@”start new thread …”);
[NSThread detachNewThreadSelector:@selector(runOnNewThread) toTarget:self withObject:nil];
while (!end) {
NSLog(@”runloop…”);
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
NSLog(@”runloop end.”);
}
NSLog(@”ok.”);
}
-(void)runOnNewThread{
NSLog(@”run for new thread …”);
sleep(1);
end=YES;
NSLog(@”end.”);
}
但是这样做,运行时会发现,while循环后执行的语句会在很长时间后才被执行。
那是不是可以这样:
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
缩短runloop的休眠时间,看起来解决了上面出现的问题。
不过这样也又问题,runloop对象被经常性的唤醒,这违背了runloop的设计初衷。runloop的作用就是要减少cpu做无谓的空转,cpu可在空闲的时候休眠,以节约电量。
那么怎么做呢?正确的写法是:
-(void)runOnNewThread{
NSLog(@”run for new thread …”);
sleep(1);
[self performSelectorOnMainThread:@selector(setEnd) withObject:nil waitUntilDone:NO];
NSLog(@”end.”);
}
-(void)setEnd{
end=YES;
}
见黑体斜体字部分,要将直接设置变量,改为向主线程发送消息,执行方法。问题得到解决。
这里要说一下,造成while循环后语句延缓执行的原因是,runloop未被唤醒。因为,改变变量的值,runloop对象根本不知道。延缓的时长总是不定的,这是因为,有其他事件在某个时点唤醒了主线程,这才结束了while循环。那么,向主线程发送消息,将唤醒runloop,因此问题就解决了。
from CFRunLoop Reference
CFRunLoop monitors sources of input to a task and dispatches control when they become ready for processing.
Examples of input source might include user input devices,network connection,periodic or timed-delay events,and asynchronous callbacks.
Three types of objects can be monitored by a run loop:
sources(CFRunLoopSource)
timers(CFRunLoopTimer)
observers(CFRunLoopObserver)
Each sources,times or observers added to a run loop must be associated with one or more run loop modes.
There is exactly one run loop per thread.
RunLoop 是事件循环,用于schedule work 和协调输入事件.
RunLoop 接受2种不同的事件源,input sources (异步事件)和 timer sources(同步事件)
何时使用RunLoop?
1.使用ports或自定义输入源 与其他线程通讯
2.在线程中使用定时器
3.在程序中使用performSelector等方法
4.让线程周期性执行某个任务
NSRunLoop的进一步理解的更多相关文章
- iOS开发之NSRunLoop的进一步理解
http://www.cnblogs.com/pengyingh/articles/2343920.html iPhone应用开发中关于NSRunLoop的概述是本文要介绍的内容,NSRunLoop是 ...
- Java内存管理的进一步理解-模拟过程图解
Java内存管理的进一步理解-模拟过程图解--转载 java的内存管理分为: 1.堆内存:2.栈内存:3.方法区:4.本地方法区 /* 1:方法区 方法区存放装载的类数据信息包括: ...
- 对于python装饰器结合递归的进一步理解
对于python装饰器结合递归的进一步理解 代码如下: import functools def memoize(fn): print('start memoize') known = dict() ...
- iOS NSRunloop的简单理解
最近学习了下NSRunloop. 作一下简单的理解: 1.runloop与线程的关系,每一个线程创建是都会有伴有一个runloop诞生,runloop用来接收事件源,让线程执行事件.当没有事件处理时, ...
- 对iOS后台模式最多10分钟运行时间的进一步理解
在app进入后台时,系统初始默认是只有10s的处理时间,但如果10s不够,我们可以主动申请,网上流传最多的一个说法是10分钟. 但这种说法有个前提: 那就是iOS7之前,是这样 但从iOS7开始,我们 ...
- AutoResetEvent waitone set进一步理解补充
AutoResetEvent 的定义 //定义两个信号锁 AutoResetEvent ReadTxt = new AutoResetEvent(false); AutoResetEvent Uplo ...
- 对Java中字符串的进一步理解
字符串在程序开发中无处不在,也是用户交互所涉及到最频繁的数据类型,那么字符串不仅仅就是我们简单的理解的String str = "abc";一起来更加深入的看一下 在Java中,字 ...
- 通过回调函数的理解来进一步理解ajax及其注意的用法
一,再一次理解回调函数 (function($){ $.fn.shadow = function(opts){ //定义的默认的参数 var defaults = { copies: 5, opaci ...
- 『cs231n』卷积神经网络的可视化与进一步理解
cs231n的第18课理解起来很吃力,听后又查了一些资料才算是勉强弄懂,所以这里贴一篇博文(根据自己理解有所修改)和原论文的翻译加深加深理解,其中原论文翻译比博文更容易理解,但是太长,而博文是业者而非 ...
随机推荐
- 如何在SpringBoot中使用JSP ?但强烈不推荐,果断改Themeleaf吧
做WEB项目,一定都用过JSP这个大牌.Spring MVC里面也可以很方便的将JSP与一个View关联起来,使用还是非常方便的.当你从一个传统的Spring MVC项目转入一个Spring Boot ...
- React Native学习笔记之2
1:如何创建一个react native工程 首先进入到指定文件夹里面,然后在终端执行react-native init ReactNativeProject :其中ReactNativeProjec ...
- Java 计算年份,月份,剩余天数
实现很简单,两者时间进行比较...具体看下面代码! package com.text; import java.text.ParseException; import java.text.Simple ...
- VNC connect:Connection refused(10061)
在Windows机器上使用VNC Viewer访问Linux服务器,有时候会遇到"connect:Connection refused(10061)"这个错误,导致这个错误出现的原 ...
- 使用python的Flask实现一个RESTful API服务器端[翻译]
最近这些年,REST已经成为web services和APIs的标准架构,很多APP的架构基本上是使用RESTful的形式了. 本文将会使用python的Flask框架轻松实现一个RESTful的服务 ...
- 萌新笔记——git的问题(error: object file .git/objects/* is empty...)的解决方案及对git版本库文件的了解
由于操作不当,导致git版本库出了大问题,如下所示: error: object file .git/objects/8b/61d0135d3195966b443f6c73fb68466264c68e ...
- 2-sql基本操作
sql基本操作 一.Sqlplus常用命令 1.查看oracle数据库的进程 2.查看oracle数据库运行状态 3.显示实例名(数据库名) 4.用sys账户登陆到数据库 5.解锁账户scott,并登 ...
- Execl数据导入sql server方法
在日常的程序开发过程中,很多情况下,用户单位给予开发人员的数据往往是execl或者是access数据,如何把这些数据转为企业级是数据库数据呢,下面就利用sqlserver自带的功能来完成此项任务. 首 ...
- java基础算法之插入排序
一.插入排序介绍 插入排序(Insertion Sort)是一种简单直观的排序算法.它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入.插入排序在实现上,通 ...
- python爬虫学习(7) —— 爬取你的AC代码
上一篇文章中,我们介绍了python爬虫利器--requests,并且拿HDU做了小测试. 这篇文章,我们来爬取一下自己AC的代码. 1 确定ac代码对应的页面 如下图所示,我们一般情况可以通过该顺序 ...