`
huobengluantiao8
  • 浏览: 1051004 次
文章分类
社区版块
存档分类
最新评论

NSThread 、NSRunLoop 和 Dispatch Queue

 
阅读更多

iOS多线程编程中,NSOperation和NSOperationQueue无疑是最常用的,它们能满足绝大部分情况下的线程操作。但在完成一些特殊的任务时,我们还是要使用的NSThread和NSRunLoop。

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并行编程拥有许多优点:不需要显式地创建线程和为线程分配栈空间、代码极大地简化、块语法支持。



分享到:
评论

相关推荐

    RunLoop示例

    `NSRunLoop`与`NSThread`和`Dispatch Queue`紧密相关,它们都是Apple的多线程编程模型的重要组成部分。这篇文章的示例将深入探讨这三个概念及其相互作用。 首先,让我们理解`NSThread`。`NSThread`是Objective-C中...

    DispatchSourceTest.zip

    1. 创建`DispatchQueue`,通常是全局队列或自定义串行队列。 2. 初始化`DispatchSource`,指定感兴趣的事件类型,如读写事件。 3. 将`DispatchSource`与队列关联,使用`dispatch_source_set_event_handler`设置事件...

    关于iOS多线程,你看我就够了 - 简书1

    基本概念包括队列(Dispatch Queue)和任务(Dispatch Work Item)。主队列是同步执行的,全局队列是异步执行的,还可以自定义串行队列。使用`dispatch_async`或`dispatch_sync`来提交任务到队列。 最后,...

    iOS进阶1

    - `dispatch_queue_create`可创建自定义队列,`DISPATCH_QUEUE_CONCURRENT`表示并发队列,`NULL`表示串行队列。 - `dispatch_apply`用于快速迭代遍历,可在队列上并行执行指定次数的任务,但顺序不一定保证。 - `...

    多线程编程指南

    2. **GCD (Grand Central Dispatch)**:Apple推出的一种轻量级并行处理框架,使用Block语法,提供队列(Dispatch Queue)来调度任务,分为串行队列和并行队列。串行队列确保任务按顺序执行,而并行队列则可以同时...

    线程的使用

    此外,我们还会提到GCD(Grand Central Dispatch),它是Apple提供的多线程解决方案,它包括Queue(队列)、Task(任务)和Thread(线程)。GCD使用队列来管理任务,它可以更高效地调度线程,减轻程序员管理线程的...

    iOS并发编程指南文档分享

    Operation和OperationQueue是Apple提供的面向对象的并发模型,它们比GCD更易于理解和使用。Operation是一个可定制的任务对象,可以包含复杂的逻辑;OperationQueue负责管理这些操作的执行。 1. Operation的继承:...

    IOS面试题总结,集合了很多培训机构的老师精心整理出来的,绝对给力

    - OperationQueue和Operation的使用。 - NSThread、NSRunLoop的理解及其与GCD的区别。 7. **动画** - Core Animation的理解,如何创建基本的CAAnimation。 - UIView动画的使用,包括transition、...

    downloadpicwiththread

    iOS提供了几种多线程技术,如GCD(Grand Central Dispatch)、NSOperationQueue、NSThread以及NSRunLoop。 - **GCD**:是Apple提供的多线程解决方案,它简化了线程管理和同步。可以使用`dispatch_queue_create`...

    多线程编程

    在《iOS并发编程指南》中,你将学习到如何利用GCD来实现并发,包括使用`dispatch_queue_create`创建队列、`dispatch_async`异步执行任务、`dispatch_sync`同步执行任务,以及如何使用barrier tasks来确保特定任务在...

    IOS应用源码——多线程.zip

    开发者可以通过`dispatch_queue_create`创建队列,`dispatch_async`或`dispatch_sync`来异步或同步地执行任务。 3. **NSOperationQueue**:这是Objective-C的一个类,它提供了对GCD的更高层次的封装,支持操作的...

Global site tag (gtag.js) - Google Analytics