iOS 多线程


iOS 多线程

内容概述

  • NSThread
  • Block基础
  • Grand Central Dispatch (GCD)
  • 操作对象(Operation Object)

多线程在任何程序开发中都是非常重要的,在有些情况下甚至的必须的,例如,网络服务器、文件上传、文件下载。许多耗时的操作都应该在另外的线程中运行,才不会使程序阻塞,提高程序的效率。iOS 程序开发也提供了多种多线程处理方案,包括:NSThread、操作对象队列和Grand Central Dispatch (GCD)等。

25.1 NSThread

NSThread是iOS中最基本的线程处理方法,目前逐渐被Operation Object和GCD所取代。线程的操作包括:线程的初始化、启动停止线程、检测线程的状态等。创建并启动一个NSThread的方法如下:

  1. 初始化NSThread并指定一个selector方法,调用start方法启动线程。
NSThread *t = [[NSThread alloc]initWithTarget:self selector:@selector(loop) object:nil];

// 设置线程名称

t.name = @"My Thread";

// 启动线程

[t start];
  1. 调用NSThread的静态方法。
[NSThread detachNewThreadSelector:@selector(loop) toTarget:self withObject:nil];
  1. 定义一个类继承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可以使用三种调度队列:

  1. Main queue,在该queue中定义的任务执行在程序的主线程中,一般是和UI相关的任务,例如,更新UI界面的显示,获得queue的方法是:dispatch_get_main_queue。

  2. Concurrent queue,在该queue中定义的任务执行在用户线程中,一般是后台长时间执行的任务,例如,下载文件,获得queue的方法是:dispatch_get_global_queue。

  3. Serial queue,在该queue中定义的任务是序列执行的,即先进先出(FIFO)。获得queue的方法是:dispatch_queue_create,并且要使用dispatch_release方法释放。

下面通过一个按钮来演示GCD的用法,该案例模拟一个网络下载任务,并使用进度条显示当前进度的程序。实现步骤如下:

  1. 创建一个项目,在xib中添加一个进度条和一个按钮,在.h中声明进度条的属性和按钮的单击事件。
@interface AmakerViewController : UIViewController

// 进度条属性

@property (strong, nonatomic) IBOutlet UIProgressView *myProgress;

// 启动方法

- (IBAction)start:(id)sender;

@end
  1. 实现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];

            });

        }

    });

}
  1. 程序运行结果如下所示。

图 25.1 使用线程更新进度条

25.4 操作对象(Operation Object)

在iOS多线程处理中的另外一种处理方法是操作对象,即把要执行的任务封装整操作对象NSOpetation,并将操作对象放到操作队列NSOperationQueue中,可以设置这些任务的执行顺序以及依赖关系等。

使用操作对象处理多线程使用到如下类:

  1. 操作队列NSOperationQueue。

  2. 操作对象NSOperation。

  3. 操作对象的子类NSInvocationOperation,可以使用该类指定一个selector来执行任务。

  4. 操作对象的子类NSBlockOperation,可以使用该类指定一个Block来执行任务。

下面通过一个案例来演示操作对象的使用,该案例在界面上添加一个按钮,创建一个操作类继承NSOperation,自定义一个初始化方法,并覆盖main方法,循环打印那个操作在执行。创建一个操作队列,并实例化两个操作添加到操作队列。步骤如下:

  1. 创建一个项目,在界面上添加一个按钮,在.h文件中添加单击事件方法。
- (IBAction)test1:(id)sender;
  1. 创建一个操作对象继承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(@"%@&#39;s i=%d",self.name,i);

    }

}

@end
  1. 在按钮的单击方法中,创建一个队列,两个操作,并将两个操作添加到队列中。
- (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];

}
  1. 程序运行结果如下所示。
2013-04-22 16:19:02.997 chapter25-05[2147:1b03] Opetation1&#39;s i=0

2013-04-22 16:19:02.997 chapter25-05[2147:1303] Opetation2&#39;s i=0

2013-04-22 16:19:04.001 chapter25-05[2147:1b03] Opetation1&#39;s i=1

2013-04-22 16:19:04.001 chapter25-05[2147:1303] Opetation2&#39;s i=1

2013-04-22 16:19:05.003 chapter25-05[2147:1303] Opetation2&#39;s i=2

2013-04-22 16:19:05.003 chapter25-05[2147:1b03] Opetation1&#39;s i=2

2013-04-22 16:19:06.005 chapter25-05[2147:1303] Opetation2&#39;s i=3

2013-04-22 16:19:06.005 chapter25-05[2147:1b03] Opetation1&#39;s i=3

2013-04-22 16:19:07.008 chapter25-05[2147:1303] Opetation2&#39;s i=4

2013-04-22 16:19:07.008 chapter25-05[2147:1b03] Opetation1&#39;s i=4

2013-04-22 16:19:08.010 chapter25-05[2147:1303] Opetation2&#39;s i=5

2013-04-22 16:19:08.010 chapter25-05[2147:1b03] Opetation1&#39;s i=5

2013-04-22 16:19:09.013 chapter25-05[2147:1303] Opetation2&#39;s i=6

2013-04-22 16:19:09.013 chapter25-05[2147:1b03] Opetation1&#39;s i=6

2013-04-22 16:19:10.015 chapter25-05[2147:1b03] Opetation1&#39;s i=7

2013-04-22 16:19:10.015 chapter25-05[2147:1303] Opetation2&#39;s i=7

2013-04-22 16:19:11.017 chapter25-05[2147:1b03] Opetation1&#39;s i=8

2013-04-22 16:19:11.017 chapter25-05[2147:1303] Opetation2&#39;s i=8

2013-04-22 16:19:12.020 chapter25-05[2147:1b03] Opetation1&#39;s i=9

2013-04-22 16:19:12.020 chapter25-05[2147:1303] Opetation2&#39;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(@"%@&#39;s i=%d",@"Operation1",i);

        }

    }];

    // 创建操作对象2

    NSBlockOperation *ope2 = [NSBlockOperation blockOperationWithBlock:^{

        for (int i=0; i<10; i++) {

            [NSThread sleepForTimeInterval:1];

            NSLog(@"%@&#39;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(@"%@&#39;s i=%d",@"Operation1",i);

        }

    }];

    // 创建操作对象2

    NSBlockOperation *ope2 = [NSBlockOperation blockOperationWithBlock:^{

        for (int i=0; i<5; i++) {

            [NSThread sleepForTimeInterval:1];

            NSLog(@"%@&#39;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&#39;s i=0

2013-04-22 16:49:28.120 chapter25-05[2416:1303] Operation1&#39;s i=1

2013-04-22 16:49:29.122 chapter25-05[2416:1303] Operation1&#39;s i=2

2013-04-22 16:49:30.124 chapter25-05[2416:1303] Operation1&#39;s i=3

2013-04-22 16:49:31.126 chapter25-05[2416:1303] Operation1&#39;s i=4

2013-04-22 16:49:32.134 chapter25-05[2416:61b] Operation2&#39;s i=0

2013-04-22 16:49:33.136 chapter25-05[2416:61b] Operation2&#39;s i=1

2013-04-22 16:49:34.138 chapter25-05[2416:61b] Operation2&#39;s i=2

2013-04-22 16:49:35.139 chapter25-05[2416:61b] Operation2&#39;s i=3

2013-04-22 16:49:36.140 chapter25-05[2416:61b] Operation2&#39;s i=4