iOS 多线程 iOS 网络编程 iOS GPS 定位应用 iOS 多线程 内容概述 NSThread Block基础 Grand Central Dispatch (GCD) 操作对象(Operation Object) 多线程在任何程序开发中都是非常重要的,在有些情况下甚至的必须的,例如,网络服务器、文件上传、文件下载。许多耗时的操作都应该在另外的线程中运行,才不会使程序阻塞,提高程序的效率。iOS 程序开发也提供了多种多线程处理方案,包括:NSThread、操作对象队列和Grand Central Dispatch (GCD)等。 25.1 NSThread NSThread是iOS中最基本的线程处理方法,目前逐渐被Operation Object和GCD所取代。线程的操作包括:线程的初始化、启动停止线程、检测线程的状态等。创建并启动一个NSThread的方法如下: 初始化NSThread并指定一个selector方法,调用start方法启动线程。 NSThread *t = [[NSThread alloc]initWithTarget:self selector:@selector(loop) object:nil]; // 设置线程名称 t.name = @"My Thread"; // 启动线程 [t start]; 调用NSThread的静态方法。 [NSThread detachNewThreadSelector:@selector(loop) toTarget:self withObject:nil]; 定义一个类继承NSThread,并覆盖main方法。 #import <Foundation/Foundation.h> @interface MyThread : NSThread @end @implementation MyThread -(void)main{ for (int i=0; i<10; i++) { NSLog(@"i=%d",i); [NSThread sleepForTimeInterval:1]; } } @end 另外,我们还可以检测当前线程的运行状态,例如是否被取消、是否正在执行、是否执行结束和是否是主线程等。 // 判断线程状态 if (t.isCancelled) { NSLog(@"已经被取消"); }else{ NSLog(@"未被取消"); } if (t.isExecuting) { NSLog(@"正在执行"); } if (t.isFinished) { NSLog(@"已经结束"); } if (t.isMainThread) { NSLog(@"是主线程"); }else{ NSLog(@"非主线程"); } 由于,NSThread目前在高版本的iOS SDK中已经很少用到,这里我们将不再赘述其他更多内容。 25.2 Block 基础 Block 顾名思义就是一个代码块单元,帮助我们组织独立的代码段,并提高复用性和可读性。Block 类似c语言中函数指针,是一种方法回调机制。 Block是对C语言的扩展,用来实现匿名内部函数的特性,Block可以实现函数的嵌套,Block可以访问函数的内部变量。Block语法比较怪异,给初学者带来一些困难。 1.2.1 Block的声明与调用 Block的语法结构是这样的:返回值(^block名称)(参数类型列表)=^(参数列表){函数体;}; 下面是一个声明一个没有返回值,也没有参数的Block,该Block使用NSLog打印一句话。我们在main主函数中可以调用它。 void(^myBloc)(void)=^(void){ NSLog(@"Hello Block"); }; int main(int argc, const char * argv[]) { @autoreleasepool { myBloc(); } return 0; } 程序输入结构如下: Hello Block 1.2.2 有返回值和参数的Block 下面我们通过Block定义一个两个数求和的功能块,并调用它。代码如下: // 求和 int(^sumBlock)(int,int)=^(int a,int b){ return a+b; }; 调用: int s = sumBlock(1,2); NSLog(@"sum=%d",s); 程序输出: sum=3 25.3 Grand Central Dispatch (GCD) Grand Central Dispatch 的中文意思是大中心调度,一般我们简称GCD。是苹果主推的多线程处理机制,该多线程处理机制在多核CPU状态下,性能很高。GCD一般和Block一起使用,在Block回调中处理程序操作。GCD声明了一系列以dispatch打头的方法来实现多线程的操作,例如,获得线程队列、启动同步、异步线程等。 下面代码演示了如何定义一个线程队列,并执行一个异步操作。 - (IBAction)test:(id)sender { // 获得全局队列 dispatch\_queue\_t queue = dispatch\_get\_global\_queue(DISPATCH\_QUEUE\_PRIORITY\_DEFAULT, 0); // 执行异步请求 dispatch\_async(queue, ^{ [self loop]; }); } // 循环 -(void)loop{ for (int i=0; i<10; i++) { // 睡眠1秒 [NSThread sleepForTimeInterval:1]; NSLog(@"i=%d",i); } } 在iOS SDK可以使用三种调度队列: Main queue,在该queue中定义的任务执行在程序的主线程中,一般是和UI相关的任务,例如,更新UI界面的显示,获得queue的方法是:dispatch_get_main_queue。 Concurrent queue,在该queue中定义的任务执行在用户线程中,一般是后台长时间执行的任务,例如,下载文件,获得queue的方法是:dispatch_get_global_queue。 Serial queue,在该queue中定义的任务是序列执行的,即先进先出(FIFO)。获得queue的方法是:dispatch_queue_create,并且要使用dispatch_release方法释放。 下面通过一个按钮来演示GCD的用法,该案例模拟一个网络下载任务,并使用进度条显示当前进度的程序。实现步骤如下: 创建一个项目,在xib中添加一个进度条和一个按钮,在.h中声明进度条的属性和按钮的单击事件。 @interface AmakerViewController : UIViewController // 进度条属性 @property (strong, nonatomic) IBOutlet UIProgressView *myProgress; // 启动方法 - (IBAction)start:(id)sender; @end 实现start事件方法,点击按钮后,启动一个异步线程更新进度值,在主线程中更新进度条的进度。 - (IBAction)start:(id)sender { // 声明队列 dispatch\_queue\_t queue = dispatch\_get\_global\_queue(DISPATCH\_QUEUE\_PRIORITY\_DEFAULT, 0); // 异步任务 dispatch\_async(queue, ^{ // 总进度 float total = 0.0; // 进度为1.0时退出 while (total<=1.0) { // 增加进度 total+=0.1; [NSThread sleepForTimeInterval:1]; // 更新UI在主线程中 dispatch\_async(dispatch\_get\_main\_queue(), ^{ [self.myProgress setProgress:total]; }); } }); } 程序运行结果如下所示。 25.4 操作对象(Operation Object) 在iOS多线程处理中的另外一种处理方法是操作对象,即把要执行的任务封装整操作对象NSOpetation,并将操作对象放到操作队列NSOperationQueue中,可以设置这些任务的执行顺序以及依赖关系等。 使用操作对象处理多线程使用到如下类: 操作队列NSOperationQueue。 操作对象NSOperation。 操作对象的子类NSInvocationOperation,可以使用该类指定一个selector来执行任务。 操作对象的子类NSBlockOperation,可以使用该类指定一个Block来执行任务。 下面通过一个案例来演示操作对象的使用,该案例在界面上添加一个按钮,创建一个操作类继承NSOperation,自定义一个初始化方法,并覆盖main方法,循环打印那个操作在执行。创建一个操作队列,并实例化两个操作添加到操作队列。步骤如下: 创建一个项目,在界面上添加一个按钮,在.h文件中添加单击事件方法。 - (IBAction)test1:(id)sender; 创建一个操作对象继承NSOperation,添加初始化方法,并覆盖main方法。 #import <Foundation/Foundation.h> // 自定义一个操作类,继承NSOperation @interface MyOpetation : NSOperation // 操作名称属性 @property(nonatomic,strong)NSString *name; // 初始化方法 -(id)initWithName:(NSString*)name; @end #import "MyOpetation.h" @implementation MyOpetation // 实现初始化方法 -(id)initWithName:(NSString*)name{ self = [super init]; if (self) { self.name = name; } return self; } // 覆盖main方法 -(void)main{ // 循环打印那个操作在执行 for (int i=0; i<10; i++) { [NSThread sleepForTimeInterval:1]; NSLog(@"%@'s i=%d",self.name,i); } } @end 在按钮的单击方法中,创建一个队列,两个操作,并将两个操作添加到队列中。 - (IBAction)test1:(id)sender { // 创建线程队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; // 创建操作1 MyOpetation *ope = [[MyOpetation alloc]initWithName:@"Opetation1"]; // 添加到队列 [queue addOperation:ope]; // 创建操作2 MyOpetation *ope2 = [[MyOpetation alloc]initWithName:@"Opetation2"]; // 添加到队列 [queue addOperation:ope2]; } 程序运行结果如下所示。 2013-04-22 16:19:02.997 chapter25-05[2147:1b03] Opetation1's i=0 2013-04-22 16:19:02.997 chapter25-05[2147:1303] Opetation2's i=0 2013-04-22 16:19:04.001 chapter25-05[2147:1b03] Opetation1's i=1 2013-04-22 16:19:04.001 chapter25-05[2147:1303] Opetation2's i=1 2013-04-22 16:19:05.003 chapter25-05[2147:1303] Opetation2's i=2 2013-04-22 16:19:05.003 chapter25-05[2147:1b03] Opetation1's i=2 2013-04-22 16:19:06.005 chapter25-05[2147:1303] Opetation2's i=3 2013-04-22 16:19:06.005 chapter25-05[2147:1b03] Opetation1's i=3 2013-04-22 16:19:07.008 chapter25-05[2147:1303] Opetation2's i=4 2013-04-22 16:19:07.008 chapter25-05[2147:1b03] Opetation1's i=4 2013-04-22 16:19:08.010 chapter25-05[2147:1303] Opetation2's i=5 2013-04-22 16:19:08.010 chapter25-05[2147:1b03] Opetation1's i=5 2013-04-22 16:19:09.013 chapter25-05[2147:1303] Opetation2's i=6 2013-04-22 16:19:09.013 chapter25-05[2147:1b03] Opetation1's i=6 2013-04-22 16:19:10.015 chapter25-05[2147:1b03] Opetation1's i=7 2013-04-22 16:19:10.015 chapter25-05[2147:1303] Opetation2's i=7 2013-04-22 16:19:11.017 chapter25-05[2147:1b03] Opetation1's i=8 2013-04-22 16:19:11.017 chapter25-05[2147:1303] Opetation2's i=8 2013-04-22 16:19:12.020 chapter25-05[2147:1b03] Opetation1's i=9 2013-04-22 16:19:12.020 chapter25-05[2147:1303] Opetation2's i=9 除了继承NSOperation之外,还可以使用NSInvocationOperation操作对象,使用NSInvocationOperation操作对象可以不使用子类,直接定义要执行的任务方法。下面代码定义了一个队列,两个操作,循环打印那个操作在执行。 - (IBAction)test2:(id)sender { // 创建线程队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; // 创建操作对象,指定任务执行方法 NSInvocationOperation *ope1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(loop:) object:@"Operation1"]; // 将任务添加到队列 [queue addOperation:ope1]; // 创建操作对象,指定任务执行方法 NSInvocationOperation *ope2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(loop:) object:@"Operation2"]; // 将任务添加到队列 [queue addOperation:ope2]; } 我们还可以使用NSBlockOperation操作对象,以Block的方式来使用该操作对象。要执行的任务代码被定义在该Block中。下面代码实现了相同的功能,只是使用了Block格式。 - (IBAction)test3:(id)sender { // 创建线程队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; // 创建操作对象1 NSBlockOperation *ope1 = [NSBlockOperation blockOperationWithBlock:^{ for (int i=0; i<10; i++) { [NSThread sleepForTimeInterval:1]; NSLog(@"%@'s i=%d",@"Operation1",i); } }]; // 创建操作对象2 NSBlockOperation *ope2 = [NSBlockOperation blockOperationWithBlock:^{ for (int i=0; i<10; i++) { [NSThread sleepForTimeInterval:1]; NSLog(@"%@'s i=%d",@"Operation2",i); } }]; // 将操作对象,添加到队列 [queue addOperation:ope1]; [queue addOperation:ope2]; } 另外,操作之间可以添加依赖关系,例如B操作依赖A操作,那么,只有A操作执行完B操作才执行。实现依赖代码如下: - (IBAction)test4:(id)sender { // 创建线程队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; // 创建操作对象1 NSBlockOperation *ope1 = [NSBlockOperation blockOperationWithBlock:^{ for (int i=0; i<5; i++) { [NSThread sleepForTimeInterval:1]; NSLog(@"%@'s i=%d",@"Operation1",i); } }]; // 创建操作对象2 NSBlockOperation *ope2 = [NSBlockOperation blockOperationWithBlock:^{ for (int i=0; i<5; i++) { [NSThread sleepForTimeInterval:1]; NSLog(@"%@'s i=%d",@"Operation2",i); } }]; // 操作1依赖操作1 [ope2 addDependency:ope1]; // 将操作对象,添加到队列 [queue addOperation:ope1]; [queue addOperation:ope2]; } 程序运行结果如下所示。 2013-04-22 16:49:27.117 chapter25-05[2416:1303] Operation1's i=0 2013-04-22 16:49:28.120 chapter25-05[2416:1303] Operation1's i=1 2013-04-22 16:49:29.122 chapter25-05[2416:1303] Operation1's i=2 2013-04-22 16:49:30.124 chapter25-05[2416:1303] Operation1's i=3 2013-04-22 16:49:31.126 chapter25-05[2416:1303] Operation1's i=4 2013-04-22 16:49:32.134 chapter25-05[2416:61b] Operation2's i=0 2013-04-22 16:49:33.136 chapter25-05[2416:61b] Operation2's i=1 2013-04-22 16:49:34.138 chapter25-05[2416:61b] Operation2's i=2 2013-04-22 16:49:35.139 chapter25-05[2416:61b] Operation2's i=3 2013-04-22 16:49:36.140 chapter25-05[2416:61b] Operation2's i=4 iOS 网络编程 iOS GPS 定位应用