NSThread 、NSRunLoop 和 Dispatch Queue
NSThread很好理解,它等同于Java中的Thread类。
NSRunLoop却不太好理解。从字面上说,RunLoop可以翻译成“运行回路”或“运行循环”,我们可以把它看成是一种特殊的循环结构——我们知道for或者while循环语句,其实NSRunLoop就是一种类似的循环,只不过它比for/while要复杂得多。我相信你已经看过苹果的《多线程编程指南》,其中“Run Loops”有专门的介绍。但这篇文档太长了,我相信你很难把它从头到尾都看完。本文会以实例的方式演示RunnLoop的使用,没有过于复杂的内容,保证你能看懂——有时候恰恰是我们自己把事情搞复杂了。
一、当前RunLoop
当前RunLoop是指用 CFRunLoopGetCurrent 函数获取的RunLoop,它是运行在当前线程的RunLoop,在没有使用子线程的情况下,当前线程也可能是应用程序的主线程。如果你直接在主线程的方法里使用CFRunLoopGetCurrent 函数,那么得到的RunLoop就是主线程的RunLoop。
新建Single View Application。在ViewController.xib中拖入一个按钮和一个TextView。并将两个对象都连接到源代码。
实现按钮的Action如下:
- (IBAction)RunOrStop:(id)sender {
if ([@"Run"isEqualToString:btRun.titleLabel.text]) {
tvList.text=nil;
[btRunsetTitle:@"Stop"forState:UIControlStateNormal];
CFRunLoopTimerRef timer;
CFRunLoopTimerContext timer_context;
bzero(&timer_context, sizeof(timer_context));
timer_context.info = self;
timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent(), 2, 0, 0, _timer, &timer_context);
mRunLoopRef=CFRunLoopGetCurrent();
CFRunLoopAddTimer( mRunLoopRef, timer, kCFRunLoopCommonModes);
CFRunLoopRun();
}else{
[btRunsetTitle:@"Run"forState:UIControlStateNormal];
if ( mRunLoopRef )
CFRunLoopStop( mRunLoopRef );
}
}
按钮btRun是一个乒乓式的按钮,用户点击它时它的title会在Run和Stop之间切换。
RunLoop是一种有源循环,它由各种“输入源”进行驱动。类似地,for语句由循环变量进行驱动,当循环变量到达某个值,循环中止。RunLoop的输入源可以有许多类型,这里我们只使用了最普通的定时器 CFRunLoopTimerRef。构造定时器时,需要一个 CFRunLoopTimerContext作为初始化时的上下文。
这个上下文是个结构体(有5个成员),其中info成员是void*类型(即id类型),对于我们来说可以把一些有用的对象传递进去,比如说self——self相当有用,因为self便于我们在c函数中调用成员方法:
CFRunLoopTimerContext timer_context;
bzero(&timer_context, sizeof(timer_context));
timer_context.info = self;
其他4个结构体成员对于我们来说都没有意义。因此上面3句其实也可以写成:
CFRunLoopTimerContext timer_context={0, self, NULL, NULL, NULL};
接下来就是用这个上下文构造timer:
timer= CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent(), 2, 0, 0, _timer, &timer_context);
除了NULL和0之外的几个参数分别是:定时器启动时间、间隔秒数、定时执行函数、上下文指针。
其他参数我们都明白了,除了定时执行函数_timer——我们呆会再讨论它。接下来:
mRunLoopRef=CFRunLoopGetCurrent();一句获取当前线程的RunLoop;同时把RunLoop保存在mRunLoopRef中。mRunLoopRef需要声明为静态变量,这样较方便我们在c函数_timer中访问:
staticCFRunLoopRef mRunLoopRef;
这一句将Timer添加到RunLoop:
CFRunLoopAddTimer( mRunLoopRef, timer, kCFRunLoopCommonModes);
这一句启动RunLoop:
CFRunLoopRun();
实现_timer()函数如下:
void _timer(CFRunLoopTimerRef timer __unused, void *info)
{
loops++;
id obj=(id)info;
[obj performSelectorOnMainThread:@selector(updateTextView) withObject:nilwaitUntilDone:NO];
}
_timer函数根据约定,为CFRunLoopTimerCallBack结构,因此它拥有两个参数:
CFRunLoopTimerRef timer, void *info
loops变量也声明为静态的(方便在c函数中调用),没有什么意义,我们仅仅是用来记录循环次数的。
info参数(实际上我们指定了一个self对象)在这里排上了用场,我们将会在这里调用self的updateTextView方法。因为updateTextView方法会对UI进行更新,根据UKit的规定,对UI进行刷新应当在主线程中进行,所以我们用performSelectorOnMainThread来强制在主线程执行updateView方法。
updateView方法实现如下:
-(void) updateTextView{
tvList.text=[NSStringstringWithFormat:@"%@\n%d",tvList.text,loops];
// Scroll to the bottom of TextView
NSRange range;
range = NSMakeRange ([[tvListtext] length], 0);
[tvListscrollRangeToVisible:range];
}
运行程序,当我们点击Run按钮,RunLoop循环会一遍一遍地去执行,同时TextView中会显示执行的循环次数。
等等,为什么会这样?
当你点击“Stop”按钮,发现RunLoop并不会停下来。相反,当你再次点击“Run”按钮之后,RunLoop似乎循环得更快了!基本上达到1秒钟执行一次。如果你反复点击“Stop/Run”按钮,循环会越来越快!
CFRunLoopStop 似乎并没有被执行。其实,getCurrentRunLoop拿到的是当前线程的RunLoop,对于现在的情况,实际上是主线程的RunLoop。你在主线程的RunLoop中添加了新的源,但你并没有权限停止它。而且,由于你多次点击Run按钮,RunLoop中被加入了多个Timer!这导致_timer函数在同一时间内被执行得更多。
要想停止RunLoop,你必需要创建自己的Thread,然后停止它的RunLoop。这样还可以带来另外一个好处:RunLoop不会阻塞主线程!仔细观察程序运行的结果,你会发现当Run按钮被按下时,按钮的状态始终是在Highlight状态,而不会恢复到Normal状态:
Stop按钮背景始终呈现Highlight的蓝色,是因为主线程被RunLoop阻塞了。除非RunLoop停止,按钮不会恢复为Normal状态。
那么,我们只有创建一个NSThread了。
二、子线程中的RunLoop
每个线程中都自带RunLoop,用CFRunLoopGetCurrent()函数可以获得当前线程的RunLoop。如果RunLoop中没有任何源,RunLoop不会运行。我们可以为这个RunLoop添加新的源。
修改RunOrStop方法代码为:
- (IBAction)RunOrStop:(id)sender {
if ([@"Run"isEqualToString:btRun.titleLabel.text]) {
tvList.text=nil;
[btRunsetTitle:@"Stop"forState:UIControlStateNormal];
NSThread *thread = [[NSThreadalloc] initWithTarget:selfselector:@selector(aThread) object:nil];
[thread start];
}else{
[btRunsetTitle:@"Run"forState:UIControlStateNormal];
if ( mRunLoopRef )
CFRunLoopStop( mRunLoopRef );
}
}
同时实现aThread方法如下:
-(void)aThread{
CFRunLoopTimerRef timer;
CFRunLoopTimerContext timer_context={0, self, NULL, NULL, NULL};
timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent(), 2, 0, 0, _timer, &timer_context);
mRunLoopRef=CFRunLoopGetCurrent();
CFRunLoopAddTimer( mRunLoopRef, timer, kCFRunLoopCommonModes);
CFRunLoopRun();
}
可以看到,RunOrStop方法中,我们创建了新的 NSThread对象并启动了它。而原来的RunLoop代码被移到新的线程方法aThread中来了。
运行程序,你在RunLoop运行之后可以用Stop来停止它了。同时,主线程不会再被阻塞,Stop按钮点下后,经过短暂的Highlight状态即恢复到了Normal状态,Stop按钮终于不再是怪异的蓝色了.
三、添加自定义源
我们还可以尝试定义自定义的输入源。这样,当timer源触发之后,我们还可以调用自定义源,干点别的事。触发另一个源,使用 函数 CFRunLoopSourceSignal :
voidCFRunLoopSourceSignal(CFRunLoopSourceRef source);
在_timer函数中加入此句:
if(source) CFRunLoopSourceSignal(source);
参数source指定要触发的输入源。
我们修改aThread方法如下:
-(void)aThread{
CFRunLoopSourceContext source_context;
bzero(&source_context, sizeof(source_context));
source_context.perform = _perform;
source = CFRunLoopSourceCreate(NULL, 0,&source_context);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
… …
}
其中,source被声明为静态变量:
staticCFRunLoopSourceRef source;
然后实现_perform函数:
void _perform(void *info)
{
if (loops%10==0) {
CFRunLoopStop(mRunLoopRef);
printf("loops=%d,RunLoop is stopped.\n",loops);
}
}
最后修改RunOrStop方法,注释以下语句,因为RunLoop的停止我们交给_perform函数来做了:
// [btRun setTitle:@"Stop" forState:UIControlStateNormal];
……
// [btRun setTitle:@"Run"forState:UIControlStateNormal];
// if ( mRunLoopRef )
// CFRunLoopStop( mRunLoopRef );
现在执行程序,RunLoop每运行10次就会自动停下来:
loops=10,Run Loop isstopped.
loops=20,Run Loop isstopped.
loops=30,Run Loop isstopped.
示例代码下载:http://download.csdn.net/detail/kmyhy/4426395
四、dispatch source
在iOS异步编程模型中,线程是最传统的解决方案。然而线程模型仍然有着一些为人所诟病的缺点:难于编写、可伸缩性不强。iOS通过dispathqueue和dispatch source来支持并行编程(参考苹果文档《 Concurrency Programming Guide 》)。上述代码也可以用并行并行编程方式来解决。
修改RunoOrStop:的代码为:
- (IBAction)RunOrStop:(id)sender {
source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_event_handler(source, ^{
if (loops%10==0) {
dispatch_source_cancel(source);
dispatch_release(source);
dispatch_source_cancel(timer);
dispatch_release(timer);
printf("loops=%d,Dispatchsource is stopped.\n",loops);
}
});
dispatch_resume(source);
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
loops++;
[selfperformSelectorOnMainThread:@selector(updateTextView) withObject:nilwaitUntilDone:NO];
dispatch_source_merge_data(source, 1);
});
dispatch_resume(timer);
}
其中timer和source声明为外部静态变量:
staticdispatch_source_t source,timer;
运行程序。没有使用线程、没有RunLoop,但仍然实现了同样的效果。从中可以看出,dispatch queue并行编程拥有许多优点:不需要显式地创建线程和为线程分配栈空间、代码极大地简化、块语法支持。
NSThread 、NSRunLoop 和 Dispatch Queue的更多相关文章
- 装个蒜。学习下dispatch queue
dispatch queue的真髓:能串行,能并行,能同步,能异步以及共享同一个线程池. 接口: GCD是基于C语言的APT.虽然最新的系统版本中GCD对象已经转成了Objective-C对象,但AP ...
- IOS开发 GCD介绍: 基本概念和Dispatch Queue
iOS的三种多线程技术 1.NSThread 每个NSThread对象对应一个线程,量级较轻(真正的多线程) 2.以下两点是苹果专门开发的“并发”技术,使得程序员可以不再去关心线程的具体使用问题 ØN ...
- GCD之dispatch queue
GCD之dispatch queue iOS中多线程编程工具主要有: NSThread NSOperation GCD 这三种方法都简单易用,各有千秋.但无疑GCD是最有诱惑力的,因为其本身是appl ...
- GCD: 基本概念和Dispatch Queue 【转】
什么是GCD? Grand Central Dispatch或者GCD,是一套低层API,提供了一种新的方法来进行并发程序编写.从基本功能上讲,GCD有点像 NSOperationQueue,他们都允 ...
- GCD之dispatch queue深入浅出
GCD之dispatch queue深入浅出 http://blog.csdn.net/samuelltk/article/details/9452203
- GCD介绍(一): 基本概念和Dispatch Queue
什么是GCD? Grand Central Dispatch或者GCD,是一套低层API,提供了一种新的方法来进行并发程序编写.从基本功能上讲,GCD有点像NSOperationQueue,他们都允许 ...
- GCD系列 之(一):基本概念和Dispatch Queue
参考学习https://www.dreamingwish.com/article/grand-central-dispatch-basic-1.html系列文章,貌似也是翻译自他处的.觉得非常完整,就 ...
- Blocks与Dispatch Queue的使用
block是什么block是一个C level的语法以及运行时的一个特性,和标准C中的函数(函数指针)类似.用于回调函数的地方.两个对象间的通讯.实现轻量级的“代理”. blocks和C语言函数指针的 ...
- GCD 学习(三)Main&Global Dispatch Queue
摘录自:http://zhuyanfeng.com/archives/3066 Main Dispatch Queue是在主线程中执行任务的Dispatch Queue.因为主线程只有1个,所以Mai ...
随机推荐
- NSNotificationCenter基础知识
NSNotificationCenter基础知识 Notification的工作机制 1.对应用程序中其他地方发生的事件(比如增加一条数据库记录)感兴趣的对象,会向通告中心(Notificatio ...
- Express4 Route笔记
可以参考Express官网关于路由一节:http://expressjs.com/guide/routing.html 1:通过使用GET.POST方式定义主页路由,app.js: var expre ...
- FJ省队集训DAY3 T1
思路:我们考虑如果取掉一个部分,那么能影响到最优解的只有离它最近的那两个部分. 因此我们考虑堆维护最小的部分,离散化离散掉区间,然后用线段树维护区间有没有雪,最后用平衡树在线段的左右端点上面维护最小的 ...
- BZOJ1653: [Usaco2006 Feb]Backward Digit Sums
1653: [Usaco2006 Feb]Backward Digit Sums Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 207 Solved: ...
- 基于PCA和SVM的人脸识别
程序中采用的数据集是ORL人脸库,该人脸库共有400副人脸图像,40人,每人10幅,大小为112*92像素,同一个人的表情,姿势有少许变化. 程序的流程主要分为三部分,数据的预处理(PCA降维和规格化 ...
- mysql 存储过程:提供查询语句并返回查询执行影响的行数
mysql 存储过程:提供查询语句并返回查询执行影响的行数DELIMITER $$ DROP PROCEDURE IF EXISTS `p_get_select_row_number`$$ CREAT ...
- Log4j使用说明
Log4J实例应用开发 在实际编程时,要使Log4j真正在系统中运行事先还要对配置文件进行定义.定义步骤就是对Logger.Appender及Layout的分别使用.Log4j支持两种配置文件格式,一 ...
- HDU 4588 Count The Carries 计算二进制进位总数
点击打开链接 Count The Carries Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65535/32768 K (Java ...
- telnet IP不通/sybase central工具无法连接到数据库
问题描述:客户端sybase central工具无法连接到数据库 服务端操作系统:RHEL5.8_x64,安装sybase-ASE15.7,端口号4112 IP:192.168.1.220 hos ...
- 查看Oracle当前用户下的信息(用户,表视图,索引,表空间,同义词,存储过程函数,约束条件)
0.表空间 SQL>select username,default_tablespace from user_users; 查看当前用户的角色 SQL>select * from user ...