iOS开发UI篇-合理使用多线程

优点:

使用线程可以把程序中占据时间长的任务放到后台去处理, 如图片, 视频的下载。

发挥多核处理器的优势, 并发执行让系统运行的更快, 更流畅, 用户体验更好。

缺点:

大量的线程降低代码的可读性。

更多的线程需要更多的内存空间。

当多个线程对同一个资源出现争夺的时候要注意线程的安全的问题。

简介:

 iOS有三种多线程编程的技术,分别是:

1、NSThread

2、Cocoa NSOperation(iOS多线程编程之NSOperation和NSOperationQueue的使用)

3、GCD全称:Grand Central Dispatch(iOS多线程编程之Grand Central Dispatch(GCD)介绍和使用)

这三种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的。

NSThread的使用:

优点:NSThread 比其他两个轻量级。

缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销。

NSThread 有两种直接创建方式:

- (id)initWithTarget:(id)targetselector:(SEL)selectorobject:(id)argument

+ (void)detachNewThreadSelector:(SEL)aSelectortoTarget:(id)aTargetwithObject:(id)anArgument

第一个是实例方法,第二个是类方法。

1、[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];

2、NSThread* myThread = [[NSThread alloc] initWithTarget:self

selector:@selector(doSomething:)

object:nil];

[myThread start];

参数的意义:

selector :线程执行的方法,这个selector只能有一个参数,而且不能有返回值。

target  :selector消息发送的对象

argument:传输给target的唯一参数,也可以是nil

第一种方式会直接创建线程并且开始运行线程,第二种方式是先创建线程对象,然后再运行线程操作,在运行线程操作前可以设置线程的优先级等线程信息。

PS:不显式创建线程的方法:

用NSObject的类方法  performSelectorInBackground:withObject: 创建一个线程:

[Obj performSelectorInBackground:@selector(doSomething) withObject:nil];

// 下载图片的例子

@interface NSThreadViewController ()

@property (nonatomic, strong) UIImageView *imageView;

@end

@implementation NSThreadViewController

- (void)viewDidLoad

{

[super viewDidLoad];

self.view.backgroundColor = [UIColor greenColor];

// 创建imageView

self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, [UIScreen mainScreen].bounds.size.width - 200, 200)];

self.imageView.backgroundColor = [UIColor yellowColor];

[self.view addSubview:self.imageView];

// 先创建线程对象

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"http://fdfs.xmcdn.com/group10/M03/44/66/wKgDaVWl_k-h7CEtAADjux5j-08990.jpg"];

[thread start];


- (void)downloadImage:(NSString *)urlStr

{

@autoreleasepool {

NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlStr]];

UIImage *image = [UIImage imageWithData:data];

// 回到主线程刷新UI

[self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];

NSLog(@"111");

     }

}

- (void)updateUI:(UIImage *)image

{

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

NSLog(@"%d", i);

}

self.imageView.image = image;

}

- (void)didReceiveMemoryWarning {

[super didReceiveMemoryWarning];

// Dispose of any resources that can be recreated.

}

@end

// NSThread 管理线程同步

我们演示一个经典的卖票的例子来讲NSThread的线程同步:

#import "SyncViewController.h"

@interface SyncViewController ()

{

int tickes;

int count;

// 锁对象

NSCondition *tickesCondiction;

NSLock *myLock;

NSThread *tickesThreadOne;

NSThread *tickesThreadTwo;

}

@end

@implementation SyncViewController

- (void)viewDidLoad

{

[super viewDidLoad];

self.view.backgroundColor = [UIColor greenColor];

tickes = 100;

count = 0;

// 锁对象

//    tickesCondiction = [[NSCondition alloc] init];

myLock = [[NSLock alloc] init];

tickesThreadOne = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickes) object:nil];

[tickesThreadOne setName:@"thread_01"];

[tickesThreadOne start];

tickesThreadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(saleTickes) object:nil];

[tickesThreadTwo setName:@"thread_02"];

[tickesThreadTwo start];

}

- (void)saleTickes

{

while (true) {

// 加一个锁

//        [tickesCondiction lock];

[myLock lock];

if (tickes >= 0) {

[NSThread sleepForTimeInterval:0.1];

count = 100 - tickes;

NSLog(@"当前的票数: %d, 售出: %d, 线程的名字: %@", tickes, count, [[NSThread currentThread] name]);

tickes--;

} else {

break;

}

// 解锁

//        [tickesCondiction unlock];

[myLock unlock];

}

}

- (void)didReceiveMemoryWarning {

[super didReceiveMemoryWarning];

// Dispose of any resources that can be recreated.

}

@end

如果没有线程同步的lock,卖票数可能是-1.加上lock之后线程同步保证了数据的正确性。上面例子我使用了两种锁,一种NSCondition ,一种是:NSLock。 NSCondition我已经注释了。

NSOperation和NSOperationQueue的使用:

使用 NSOperation的方式有两种,

一种是使用定义好的两个子类:

NSInvocationOperation 和 NSBlockOperation。

另一种是继承NSOperation

如果你也熟悉Java,NSOperation就和java.lang.Runnable接口很相似。和Java的Runnable一样,NSOperation也是设计用来扩展的,只需继承重写NSOperation的一个方法main。相当与java中Runnalbe的Run方法。然后把NSOperation子类的对象放入NSOperationQueue队列中,该队列就会启动并开始处理它。

我们和NSThread一样, 也下载一个图片

#import "NSOperationQueueViewController.h"

@interface NSOperationQueueViewController ()

@property (nonatomic, strong) UIImageView *upImageView;

@property (nonatomic, strong) UIImageView *downImageView;

@end

@implementation NSOperationQueueViewController

- (void)viewDidLoad

{

[super viewDidLoad];

self.view.backgroundColor = [UIColor greenColor];

self.upImageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, [UIScreen mainScreen].bounds.size.width - 200, 200)];

self.upImageView.backgroundColor = [UIColor yellowColor];

[self.view addSubview:self.upImageView];

self.downImageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, CGRectGetMaxY(self.upImageView.frame) + 40, CGRectGetWidth(self.upImageView.frame), 200)];

self.downImageView.backgroundColor = [UIColor yellowColor];

[self.view addSubview:self.downImageView];

// NSOperation

// 创建操作对象

NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"http://fdfs.xmcdn.com/group14/M09/44/AB/wKgDZFWmAF2xIIazAADw07D_0Mc440.jpg"];

NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{

NSLog(@"1111");

NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://fdfs.xmcdn.com/group14/M09/44/AB/wKgDZFWmAF2xIIazAADw07D_0Mc440.jpg"]];

UIImage *image = [UIImage imageWithData:data];

// 回到主线程刷新UI

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

self.downImageView.image = image;

}];

}];

// 创建NSOperationQueue

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

// 添加操作之间的依赖关系

[operation1 addDependency:operation2];

[queue addOperation:operation1];

[queue addOperation:operation2];

// 设置最大并发数

[queue setMaxConcurrentOperationCount:5];

}

- (void)downloadImage:(NSString *)urlStr

{

NSLog(@"222");

NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://fdfs.xmcdn.com/group11/M06/44/88/wKgDa1Wl9ryArYM_AAaulphpK_k253.png"]];

UIImage *image = [UIImage imageWithData:data];

// 回到主线程刷新UI

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

self.upImageView.image = image;

}];

}

- (void)didReceiveMemoryWarning {

[super didReceiveMemoryWarning];

// Dispose of any resources that can be recreated.

}

@end

// 在这里我们可以添加操作之间的依赖关系, 和设置最大并发数。

GCD的使用:

Grand Central Dispatch 简称(GCD)是苹果公司开发的技术,以优化的应用程序支持多核心处理器和其他的对称多处理系统的系统。这建立在任务并行执行的线程池模式的基础上的。它首次发布在Mac OS X 10.6 ,iOS 4及以上也可用。

GCD的工作原理是:让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务。

一个任务可以是一个函数(function)或者是一个block。 GCD的底层依然是用线程实现,不过这样可以让程序员不用关注实现的细节。

GCD中的FIFO队列称为dispatch queue,它可以保证先进来的任务先得到执行

dispatch queue分为下面三种:

Serial

又称为private dispatch queues,同时只执行一个任务。Serial  queue通常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。

Concurrent

又称为global dispatch queue,可以并发地执行多个任务,但是执行完成的顺序是随机的。

Main dispatch queue

它是全局可用的serial queue,它是在应用程序主线程上执行任务的。

我们看看dispatch queue如何使用。

dispatch_async

为了避免界面在处理耗时的操作时卡死,比如读取网络数据,IO,数据库读写等,我们会在另外一个线程中处理这些操作,然后通知主线程更新界面。

用GCD实现这个流程的操作比前面介绍的NSThread  NSOperation的方法都要简单。代码框架结构如下:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

// 耗时的操作

dispatch_async(dispatch_get_main_queue(), ^{

// 更新界面

});

});

// 我们还以下载图片为例:

self.imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, [UIScreen mainScreen].bounds.size.width - 200, 200)];

self.imageView.backgroundColor = [UIColor yellowColor];

[self.view addSubview:self.imageView];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

NSURL *url = [NSURL URLWithString:@"http://fdfs.xmcdn.com/group10/M03/44/66/wKgDaVWl_k-h7CEtAADjux5j-08990.jpg"];

NSData *data = [NSData dataWithContentsOfURL:url];

UIImage *image = [UIImage imageWithData:data];

// 回到主线程刷新UI

dispatch_async(dispatch_get_main_queue(), ^{

self.imageView.image = image;

});

});

是不是代码比NSThread  NSOperation简洁很多,而且GCD会自动根据任务在多核处理器上分配资源,优化程序。

系统给每一个应用程序提供了三个concurrent dispatch queues。这三个并发调度队列是全局的,它们只有优先级的不同。因为是全局的,我们不需要去创建。我们只需要通过使用函数dispath_get_global_queue去得到队列,如下

dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

这里也用到了系统默认就有一个串行队列main_queue

dispatch_queue_t mainQ = dispatch_get_main_queue();

dispatch_group_async的使用

dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了。下面是一段例子代码:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{

[NSThread sleepForTimeInterval:1];

NSLog(@"group1");

});

dispatch_group_async(group, queue, ^{

[NSThread sleepForTimeInterval:2];

NSLog(@"group2");

});

dispatch_group_async(group, queue, ^{

[NSThread sleepForTimeInterval:3];

NSLog(@"group3");

});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

NSLog(@"updateUi");

});

dispatch_release(group);

dispatch_group_async是异步的方法,运行后可以看到打印结果:

2012-09-25 16:04:16.737 gcdTest[43328:11303] group1

2012-09-25 16:04:17.738 gcdTest[43328:12a1b] group2

2012-09-25 16:04:18.738 gcdTest[43328:13003] group3

2012-09-25 16:04:18.739 gcdTest[43328:f803] updateUi

每个一秒打印一个,当第三个任务执行后,upadteUi被打印。

dispatch_barrier_async的使用

dispatch_barrier_async是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行

例子代码如下:

dispatch_queue_t queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{

[NSThread sleepForTimeInterval:2];

NSLog(@"dispatch_async1");

});

dispatch_async(queue, ^{

[NSThread sleepForTimeInterval:4];

NSLog(@"dispatch_async2");

});

dispatch_barrier_async(queue, ^{

NSLog(@"dispatch_barrier_async");

[NSThread sleepForTimeInterval:4];

});

dispatch_async(queue, ^{

[NSThread sleepForTimeInterval:1];

NSLog(@"dispatch_async3");

});

打印结果:

2012-09-25 16:20:33.967 gcdTest[45547:11203] dispatch_async1

2012-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_async2

2012-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_barrier_async

2012-09-25 16:20:40.970 gcdTest[45547:11303] dispatch_async3

请注意执行的时间,可以看到执行的顺序如上所述。

dispatch_apply

执行某个代码片段N次。

dispatch_apply(10, globalQ, ^(size_t index) {

// 执行10次

});

你可能感兴趣的:(iOS开发UI篇-合理使用多线程)