您的位置:首页 - 教程 - IOS - 正文
iOS 并行编程:Thread

1 创建线程

1.1 NSThread

      使用 NSThread 来创建线程有两个可以使用的方法:

      1) 使用 detachNewThreadSelector:toTarget:withObject:类方法来生成一个新的线程

      2) 创建一个新的 NSThread 对象,并调用它的 start 方法。

这两种创建线程的技术都在你的应用程序里面新建了一个脱离的线程。 一个脱离的线程意味着当线程退出的时候线程的资源由系统自动回收。

 1 -(void) myThreadMainMethod
 2 {
 3     printf("myThreadMainMethod\n");
 4 }
 5 
 6 -(void) testMethod
 7 {
 8     NSThread* myThread = [[NSThread alloc] initWithTarget: self
 9                                                  selector:@selector(myThreadMainMethod)
10                                                    object:nil];
11     [myThread start];
12 }

      如果你拥有一个 NSThread 对象,它的线程当前真正运行,你可以给该线程发送消息的唯一方法是在你应用程序里面的任何对象使用performSelector:onThread:withObject:waitUntilDone:方法。

 

1.2 POSIX

      Mac OS X 和 iOS 提供基于 C 语言支持的使用 POSIX 线程 API 来创建线程的方法。

 1 void* PosixThreadMainRoutine(void* data) //这是C语言的普通函数
 2 {
 3     printf("PosixThreadMainRoutine\n");
 4     return NULL;
 5 }
 6 -(void) createPOSIXThreads //这是Object-C类的成员方法
 7 {
 8     pthread_t posixThreadID;
 9     int threadError = pthread_create(&posixThreadID, NULL, &PosixThreadMainRoutine, NULL);
10 }

 

1.3 NSObject

      在 iOS 和 Mac OS X v10.5 及其之后,所有的对象都可能生成一个新的线程,并用它来执行它任意的方法。方法 performSelectorInBackground:withObject:新生成一个脱离的线程,使用指定的方法作为新线程的主体入口点。

比如,你可以使用当前对象创建一个新的线程:

1 -(void) myThreadMainMethod
2 {
3     printf("myThreadMainMethod\n");
4 }
5 
6 -(void) createNSObjectThreads
7 {
8     [self performSelectorInBackground:@selector(myThreadMainMethod) withObject:nil];
9 }

      调用该方法的效果和你在当前对象里面使用NSThread 的detachNewThreadSelector:toTarget:withObject:传递 selectore,object 作为参数的方法一样。新的线程将会被立即生成并运行,它使用默认的设置。

 

2 run loop

       Run loop 是线程相关的的基础框架的一部分。一个 run loop 就是一个事件处理的循环,用来不停的调度工作以及处理输入事件。使用 run loop 的目的是让你的线程在有工作的时候忙于工作, 而没工作的时候处于休眠状态。Run loop相当是Linux系统中的信号处理程序,需要绑定感兴趣的信号。

      Run loop 的管理并不完全自动的。你仍然需要设计你的线程代码在合适的时候启动 run loop 并正确响应输入事件。 Cocoa 和 Core Fundation 都自动在你应用程序的主线程启动一个 run loop objects 来帮助配置和管理你线程的 run loop。你的应用程序不需要显式的创建这些对象(run loop objects);每个线程,包括程序的主线程都有与之对应的 run loop object。 只有辅助线程才需要显式的运行它的 run loop。

2.1 剖析

      Run loop是用来响应产生的事件,在代码中自己实现循环控制语句,来响应接收到的事件并调用处理程序。Run loop 接收输入事件来自两种不同的来源:

  • 输入源(input source):从其它线程或应用传递来的异步事件。
  • 定时源 (timer source):是一种同步事件,发生在特定时间或者重复的时间间隔。

      如图 1所示是run loop与source之间的结构,run loop在接收到input source事件后,就会导致runUntilDate方法退出;而接收到timer source事件后,则不会退出。其中run loop除了能接收source事件外,还能产生notifications(消息),并可以指定observers来接收这些消息。

图 1

 

2.1.1 模式

      run loop 有如下的几种模式,每种模式都是用来监听input source、timer source和notifications事件。每次在运行run loop时都需要为run loop指定某种模式:

 

2.1.2 第一个程序:定时源

如下是一个简单的定时器功能,其中在0.1秒后将触发响应方法doFireTimer 。

 1 -(void) doFireTimer //定时器触发方法
 2 {
 3     printf("doFireTimer\n");
 4 }
 5 
 6 -(void) myThreadMainMethod //线程的入口函数
 7 {
 8     printf("myThreadMainMethod\n");
 9 
10     NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop]; //创建一个run loop对象
11     NSDate* futureDate = [NSDate dateWithTimeIntervalSinceNow:0.1];//设置run loop持续运行的时间
12     NSTimer* myTimer = [[NSTimer alloc] initWithFireDate:futureDate //创建一个定时器对象
13                                                 interval:0.1
14                                                   target:self
15                                                 selector:@selector(doFireTimer)
16                                                 userInfo:nil                                                  repeats:YES]; 
17     [myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode]; //将定时器对象添加到run loop对象中。
18     [myRunLoop runUntilDate:futureDate];  //启动run loop对象。
19 }

 

2.2 使用

2.2.1 获得

      为了获得当前线程的 run loop,你可以采用以下任一方式:

      1) 使用 NSRunLoop 的 currentRunLoop 类方法来返回一个NSRunLoop 对象;

      2) 使用CFRunLoopGetCurrent函数返回一个CFRunLoopRef对象。

如:

1 NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];   //方式1
2 CFRunLoopRef runLoop = CFRunLoopGetCurrent();         //方式2

 

      CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。NSRunLoop 类定义了一个 getCFRunLoop 方法,该方法返回 一个可以传递给 Core Foundation 例程的 CFRunLoopRef 类型。因为两者都指向同一个run loop。

2.2.2 启动

      run loop有三种启动方式,不同的启动方式调用不同的方法,包括以下这些:

表 3

方式

成员函数

描述

无条件

(void)run

最简单的方法,但也最不推荐使用的,可以添加或删除输入源和定时器,但是退出 run loop 的唯一方法是杀死它。没有任何办法可以让这 run loop 运行在自定义模式下。

有限时间

(void)runUntilDate:(NSDate *)limitDate

这种方式一直运行直到到某一事件到达或者规定的时间已经到期。如果是事件到达消息会被传递给相应的处理程序来处理,然后run loop 退出。你可以重新启动 run loop 来等待下一事件。如果是规定时间到期了,你只需简单的重启 run loop 或使用此段时间来做任何的其他工作。

特定模式

(BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate

这种方式是前两种方式的结合体。

 

2.2.3 退出

      有两种方法可以让 run loop 处理事件之前退出:

      1) 给 run loop 设置超时时间

      2) 通知 run loop 停止

2.2.4 时机

      仅当在为你的程序创建辅助线程的时候,你才需要显式运行一个 run loop。如果你使用 xcode提供的模板创建的程序,那么永远不需要自己显式的调用这些例程。Run loop 在要和线程有更多的交互时才需要,比如以下情况:

  • 使用端口或自定义输入源来和其他线程通信
  • 使用线程的定时器
  • Cocoa中使用任何performSelector...的方法
  • 使线程周期性工作

2.3 配置

      在你在辅助线程运行 run loop 之前,你必须至少添加一输入源或定时器给它。 如果 run loop 没有任何源需要监视的话,它会在你启动之际立马退出。

2.3.1 定时源

      为了给Run Loop配置一个定时源,只需创建一个定时器(NSTimer)对象并把它添加到NSRunLoop对象中。 其中定时源也有指针类型:CFRunLoopTimerRef,该类型是配合run loop的指针类型CFRunLoopRef使用的。并且将NSTimer 对象添加到NSRunLoop对象中有两种方式:自动和手动:

1) 自动

    这种方式是通过调用NSTimer 的静态方法scheduledTimerWithTimeInterval,从而自动将创建的NSTimer 对象添加到run loop中。

2) 手动

    这种方式是手动创建NSTimer 对象,然后调用NSRunLoop对象的addTimer方法将其添加进入run loop中。

如:

 1 NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
 2 NSDate* futureDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
 3 
 4 //自动方式,自动将NSTimer 对象添加到myRunLoop 中。
 5 [NSTimer scheduledTimerWithTimeInterval:0.2
 6             target:self
 7             selector:@selector(myDoFireTimer1:) //这里的myDoFireTimer1方法是当时间到了后,响应的成员方法。
 8             userInfo:nil
 9             repeats:YES];
10 
11 //手动方式,需要手动调用addTimer方法将myTimer添加到myRunLoop 中
12 NSTimer* myTimer = [[NSTimer alloc] initWithFireDate:futureDate
13                           interval:0.1
14                           target:self
15                           selector:@selector(myDoFireTimer2:)
16                           userInfo:nil
17                           repeats:YES];
18 [myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];

 

2.3.2 观察者

      除了安装源,你也可以添加 run loop 观察者来监视 run loop 的不同执行阶段情况。为了给 run loop 添加一个观察者,你可以创建 CFRunLoopObserverRef 不透明类型,并使用 CFRunLoopAddObserver 将它添加到你的 run loop。Run loop 观察者必须由 Core foundation 函数创建,即使是 Cocoa 程序。

 1 -(void) myThreadMainMethod
 2 {
 3     printf("myThreadMainMethod\n");
 4     
 5     NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
 6     CFRunLoopObserverContext context = {0, (__bridge void *)(self), NULL, NULL, NULL};
 7     CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
 8          if (observer)     {         CFRunLoopRef    cfLoop = [myRunLoop getCFRunLoop];         CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);     }
 9     
10     NSDate* futureDate = [NSDate dateWithTimeIntervalSinceNow:0.1];
11     NSTimer* myTimer = [[NSTimer alloc] initWithFireDate:futureDate
12                                                 interval:0.1
13                                                   target:self
14                                                 selector:@selector(doFireTimer)
15                                                 userInfo:nil                                                  repeats:YES];     [myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];
16     [myRunLoop runUntilDate:futureDate];    
17 }

 

2.3.2 输入源

      对于Apple提供的guide中有关自定义输入源和port输入源真心看不懂,为了内容的完整性,所以将此部分添加在此,若有谁清楚这块内容的,希望能介绍介绍。

3 参考文献

      [1] Threading Programming Guide.


评论: