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

NSThread 、NSRunLoop 和 Dispatch Queue的更多相关文章

  1. 装个蒜。学习下dispatch queue

    dispatch queue的真髓:能串行,能并行,能同步,能异步以及共享同一个线程池. 接口: GCD是基于C语言的APT.虽然最新的系统版本中GCD对象已经转成了Objective-C对象,但AP ...

  2. IOS开发 GCD介绍: 基本概念和Dispatch Queue

    iOS的三种多线程技术 1.NSThread 每个NSThread对象对应一个线程,量级较轻(真正的多线程) 2.以下两点是苹果专门开发的“并发”技术,使得程序员可以不再去关心线程的具体使用问题 ØN ...

  3. GCD之dispatch queue

    GCD之dispatch queue iOS中多线程编程工具主要有: NSThread NSOperation GCD 这三种方法都简单易用,各有千秋.但无疑GCD是最有诱惑力的,因为其本身是appl ...

  4. GCD: 基本概念和Dispatch Queue 【转】

    什么是GCD? Grand Central Dispatch或者GCD,是一套低层API,提供了一种新的方法来进行并发程序编写.从基本功能上讲,GCD有点像 NSOperationQueue,他们都允 ...

  5. GCD之dispatch queue深入浅出

    GCD之dispatch queue深入浅出 http://blog.csdn.net/samuelltk/article/details/9452203

  6. GCD介绍(一): 基本概念和Dispatch Queue

    什么是GCD? Grand Central Dispatch或者GCD,是一套低层API,提供了一种新的方法来进行并发程序编写.从基本功能上讲,GCD有点像NSOperationQueue,他们都允许 ...

  7. GCD系列 之(一):基本概念和Dispatch Queue

    参考学习https://www.dreamingwish.com/article/grand-central-dispatch-basic-1.html系列文章,貌似也是翻译自他处的.觉得非常完整,就 ...

  8. Blocks与Dispatch Queue的使用

    block是什么block是一个C level的语法以及运行时的一个特性,和标准C中的函数(函数指针)类似.用于回调函数的地方.两个对象间的通讯.实现轻量级的“代理”. blocks和C语言函数指针的 ...

  9. GCD 学习(三)Main&Global Dispatch Queue

    摘录自:http://zhuyanfeng.com/archives/3066 Main Dispatch Queue是在主线程中执行任务的Dispatch Queue.因为主线程只有1个,所以Mai ...

随机推荐

  1. iOS 编译64位FFMPEG

    最近因为公司项目需要捣腾FFMPEG,所以看了一下资料,现在编译成功了,记录一下. 我自己发现大概有两种方式 二者共同部分 安装装yasm或者(MAcport,我用的是yasm,这里只记录yasm的) ...

  2. 关于Android的一些理解

    Activity中写回调函数 View的回调函数-------->事件回调 Activity层--------------->生命周期回调函数.事件回调函数 Window层 Layout是 ...

  3. testNg官方文档

    官方文档:http://testng.org/doc/documentation-main.html

  4. spring framework 4 源码阅读(1) --- 前期准备

    在开始看代码之前,需要做的第一件事是下载代码. 在这里:https://github.com/spring-projects/spring-framework 下载完成了发现使用gradle做的源代码 ...

  5. Centos 6.5中安装后不能打开emacs的问题

    问题的发现过程: 安装了最新的centos版本后发现居然打不开emacs,然后在终端中输入emacs后还是不能打开,出现了下面的提示: emacs: error while loading share ...

  6. global.asax?app.config?webconfig??

    一.Global.asax 1.global.asax是什么? 一个文本文件,至于它包含写什么内容?顾名思义,global 肯定是掌管一个应用程序(application)的全局性的东西,例如应用程序 ...

  7. shell输出加颜色

    shell输出加颜色 #cat a.sh #!/bin/sh blue=`tput setaf 4` reset=`tput sgr0` echo "${blue}[INFORMATION] ...

  8. Struts2 页面url请求怎样找action

    1.我们使用最原始的方法去查找action.不同注解. struts.xml文件先配置 <!-- 新闻信息action --> <action name="newsInfo ...

  9. [RxJS] Reactive Programming - New requests from refresh clicks -- merge()

    Now we want each time we click refresh button, we will get new group of users. So we need to get the ...

  10. Android 编程下 Activity 的创建和应用退出时的销毁

    为了确保对应用中 Activity 的创建和销毁状态进行控制,所以就需要一个全局的变量来记录和销毁这些 Activity.这里的大概思路是写一个类继承 Application,并使获取该 Applic ...