标签:
多线程
当用户播放音频、下载资源、进行图像处理时往往希望做这些事情的时候其他操作不会被中 断或者希望这些操作过程中更加顺畅。在单线程中一个线程只能做一件事情,一件事情处理不完另一件事就不能开始,这样势必影响用户体验。早在单核处理器时期 就有多线程,这个时候多线程更多的用于解决线程阻塞造成的用户等待(通常是操作完UI后用户不再干涉,其他线程在等待队列中,CPU一旦空闲就继续执行, 不影响用户其他UI操作),其处理能力并没有明显的变化。如今无论是移动操作系统还是PC、服务器都是多核处理器,于是“并行运算”就更多的被提及。一件 事情我们可以分成多个步骤,在没有顺序要求的情况下使用多线程既能解决线程阻塞又能充分利用多核处理器运行能力。
常见的多线程的开发方式:
1.NSThread
2.NSOperation
3.GCD
NSThread是轻量级的多线程的开发,使用起来很简单。但是使用NSThread需要自己管理线程的生命周期。
启动线程有下面两种方式:
第一种:直接将操作添加到线程中并启动
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument
第二种:创建一个线程对象,然后调用start方法启动线程
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument
解决线路阻塞问题
在资源下载过程中,由于网络原因有时候很难保证下载时间,如果不使用
多线程可能用户完成一个下载操作需要长时间的等待,这个过程中无法进行其他操作。下面演示一个采用多线程下载图片的过程,在这个示例中点击按钮会启动一个
线程去下载图片,下载完成后使用UIImageView将图片显示到界面中。可以看到用户点击完下载按钮后,不管图片是否下载完成都可以继续操作界面,不
会造成阻塞
-
- #import "KCMainViewController.h"
-
- @interface KCMainViewController (){
- UIImageView *_imageView;
- }
-
- @end
-
- @implementation KCMainViewController
-
- - (void)viewDidLoad {
- [super viewDidLoad];
-
- [self layoutUI];
- }
-
- #pragma mark 界面布局
- -(void)layoutUI{
- _imageView =[[UIImageView alloc]initWithFrame:[UIScreen mainScreen].applicationFrame];
- _imageView.contentMode=UIViewContentModeScaleAspectFit;
- [self.view addSubview:_imageView];
-
- UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
- button.frame=CGRectMake(50, 500, 220, 25);
- [button setTitle:@"加载图片" forState:UIControlStateNormal];
-
- [button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
- [self.view addSubview:button];
- }
-
- #pragma mark 将图片显示到界面
- -(void)updateImage:(NSData *)imageData{
- UIImage *image=[UIImage imageWithData:imageData];
- _imageView.image=image;
- }
-
- #pragma mark 请求图片数据
- -(NSData *)requestData{
-
- @autoreleasepool {
- NSURL *url=[NSURL URLWithString:@"http://images.apple.com/iphone-6/overview/images/biggest_right_large.png"];
- NSData *data=[NSData dataWithContentsOfURL:url];
- return data;
- }
- }
-
- #pragma mark 加载图片
- -(void)loadImage{
-
- NSData *data= [self requestData];
-
- [self performSelectorOnMainThread:@selector(updateImage:) withObject:data waitUntilDone:YES];
- }
-
- #pragma mark 多线程下载图片
- -(void)loadImageWithMultiThread{
-
-
-
-
- [NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil];
- }
- @end
上面的程序比较简单,点击“加载图片”的按钮后会启动一个新的线程,在这个线程没有完全执行完成(图片没有显示出来的时候)的时候,用户依然可以进行其他操作,在图片下载完成之后将图片显示到界面中(这个操作瞬间完成)。更新UI的时候是在UI线程进行更新。
多个线程并发执行:
大家应该注意到不管是使
用+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target
withObject:(id)argument、- (instancetype)initWithTarget:(id)target
selector:(SEL)selector object:(id)argument 方法还是使用-
(void)performSelectorOnMainThread:(SEL)aSelector
withObject:(id)arg
waitUntilDone:(BOOL)wait方法都只能传一个参数,由于更新图片需要传递UIImageView的索引和图片数据,因此这里不妨定
义一个类保存图片索引和图片数据以供后面使用。
KCImageData.h
- <span style="font-size:14px;">/
- </span><span style="font-size:14px;"></span><pre name="code" class="objc"><span style="font-size:14px;">
-
- #import <Foundation/Foundation.h>
-
- @interface KCImageData : NSObject
-
- #pragma mark 索引
- @property (nonatomic,assign) int index;
-
- #pragma mark 图片数据
- @property (nonatomic,strong) NSData *data;
-
- @end</span>
接下来将创建多个UIImageView并创建多个线程用于往UIImageView中填充图片。
KCMainViewController.m
-
- #import "KCMainViewController.h"
- #import "KCImageData.h"
- #define ROW_COUNT 5
- #define COLUMN_COUNT 3
- #define ROW_HEIGHT 100
- #define ROW_WIDTH ROW_HEIGHT
- #define CELL_SPACING 10
-
- @interface KCMainViewController (){
- NSMutableArray *_imageViews;
- }
-
- @end
-
- @implementation KCMainViewController
-
- - (void)viewDidLoad {
- [super viewDidLoad];
-
- [self layoutUI];
- }
-
- #pragma mark 界面布局
- -(void)layoutUI{
-
- _imageViews=[NSMutableArray array];
- for (int r=0; r<ROW_COUNT; r++) {
- for (int c=0; c<COLUMN_COUNT; c++) {
- UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)];
- imageView.contentMode=UIViewContentModeScaleAspectFit;
- [self.view addSubview:imageView];
- [_imageViews addObject:imageView];
-
- }
- }
-
- UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
- button.frame=CGRectMake(50, 500, 220, 25);
- [button setTitle:@"加载图片" forState:UIControlStateNormal];
-
- [button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
- [self.view addSubview:button];
- }
-
- #pragma mark 将图片显示到界面
- -(void)updateImage:(KCImageData *)imageData{
- UIImage *image=[UIImage imageWithData:imageData.data];
- UIImageView *imageView= _imageViews[imageData.index];
- imageView.image=image;
- }
-
- #pragma mark 请求图片数据
- -(NSData *)requestData:(int )index{
-
- @autoreleasepool {
- NSURL *url=[NSURL URLWithString:@"http://images.apple.com/iphone-6/overview/images/biggest_right_large.png"];
- NSData *data=[NSData dataWithContentsOfURL:url];
- return data;
- }
- }
-
- #pragma mark 加载图片
- -(void)loadImage:(NSNumber *)index{
-
-
- NSLog(@"current thread:%@",[NSThread currentThread]);
-
- int i=[index integerValue];
-
-
- NSData *data= [self requestData:i];
-
- KCImageData *imageData=[[KCImageData alloc]init];
- imageData.index=i;
- imageData.data=data;
- [self performSelectorOnMainThread:@selector(updateImage:) withObject:imageData waitUntilDone:YES];
- }
-
- #pragma mark 多线程下载图片
- -(void)loadImageWithMultiThread{
-
- for (int i=0; i<ROW_COUNT*COLUMN_COUNT; ++i) {
- NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:[NSNumber numberWithInt:i]];
- thread.name=[NSString stringWithFormat:@"myThread%i",i];
- [thread start];
- }
- }
- @end
NSOperation有两个常用子类用于创建线程操作:NSInvocationOperation和NSBlockOperation,两种方式本质没有区别,但是是后者使用Block形式进行代码组织,使用相对方便
NSInvocationOperation
首先使用NSInvocationOperation进行一张图片的加载演示,整个过程就是:创建一个操作,在这个操作中指定调用方法和参数,然后加入到操作队列。其他代码基本不用修改,直接修加载图片方法如下:
- -(void)loadImageWithMultiThread{
-
- NSInvocationOperation *invocationOperation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
-
-
-
- NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
-
- [operationQueue addOperation:invocationOperation];
- }
NSBlockOperation
下面采用NSBlockOperation创建多个线程加载图片。
对比之前NSThread加载张图片很发现核心代码简化了不少,这里着重强调两点:
- 使用NSBlockOperation方法,所有的操作不必单独定义方法,同时解决了只能传递一个参数的问题。
- 调用主线程队列的addOperationWithBlock:方法进行UI更新,不用再定义一个参数实体(之前必须定义一个KCImageData解决只能传递一个参数的问题)。
- 使用NSOperation进行多线程开发可以设置最大并发线程,有效的对线程进行了控制(上面的代码运行起来你会发现打印当前进程时只有有限的线程被创建,如上面的代码设置最大线程数为5,则图片基本上是五个一次加载的)。
GCD是基于C语言开发的一套多线程开发机制,也是目前苹果官方推荐的多线程开发方法。前面也说过三种开发中GCD抽象层次最高,当然是用起来也最简单,只是它基于C语言开发,并不像NSOperation是面向对象的开发,而是完全面向过程的。
GCD中也有一个类似于NSOperationQueue的队列,GCD统一管理整个队列中的任务。但是GCD中的队列分为并行队列和串行队列两类:
- 串行队列:只有一个线程,加入到队列中的操作按添加顺序依次执行。
- 并发队列:有多个线程,操作进来之后它会将这些队列安排在可用的处理器上,同时保证先进来的任务优先处理。
其实在GCD中还有一个特殊队列就是主队列,用来执行主线程上的操作任务
串行队列
使用串行队列时首先要创建一个串行队列,然后调用异步调用方法,在此方法中传入串行队列和线程操作即可自动执行。下面使用线程队列演示图片的加载过程,你会发现多张图片会按顺序加载,因为当前队列中只有一个线程。
- #import "KCMainViewController.h"
- #import "KCImageData.h"
- #define ROW_COUNT 5
- #define COLUMN_COUNT 3
- #define ROW_HEIGHT 100
- #define ROW_WIDTH ROW_HEIGHT
- #define CELL_SPACING 10
-
- @interface KCMainViewController (){
- NSMutableArray *_imageViews;
- NSMutableArray *_imageNames;
- }
-
- @end
-
- @implementation KCMainViewController
-
- - (void)viewDidLoad {
- [super viewDidLoad];
-
- [self layoutUI];
- }
-
- #pragma mark 界面布局
- -(void)layoutUI{
-
- _imageViews=[NSMutableArray array];
- for (int r=0; r<ROW_COUNT; r++) {
- for (int c=0; c<COLUMN_COUNT; c++) {
- UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(c*ROW_WIDTH+(c*CELL_SPACING), r*ROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)];
- imageView.contentMode=UIViewContentModeScaleAspectFit;
- [self.view addSubview:imageView];
- [_imageViews addObject:imageView];
-
- }
- }
-
- UIButton *button=[UIButton buttonWithType:UIButtonTypeRoundedRect];
- button.frame=CGRectMake(50, 500, 220, 25);
- [button setTitle:@"加载图片" forState:UIControlStateNormal];
-
- [button addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
- [self.view addSubview:button];
-
-
- _imageNames=[NSMutableArray array];
- for (int i=0; i<ROW_COUNT*COLUMN_COUNT; i++) {
- [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]];
- }
-
- }
-
- #pragma mark 将图片显示到界面
- -(void)updateImageWithData:(NSData *)data andIndex:(int )index{
- UIImage *image=[UIImage imageWithData:data];
- UIImageView *imageView= _imageViews[index];
- imageView.image=image;
- }
-
- #pragma mark 请求图片数据
- -(NSData *)requestData:(int )index{
- NSURL *url=[NSURL URLWithString:_imageNames[index]];
- NSData *data=[NSData dataWithContentsOfURL:url];
-
- return data;
- }
-
- #pragma mark 加载图片
- -(void)loadImage:(NSNumber *)index{
-
-
- NSLog(@"thread is :%@",[NSThread currentThread]);
-
- int i=[index integerValue];
-
- NSData *data= [self requestData:i];
-
- dispatch_queue_t mainQueue= dispatch_get_main_queue();
- dispatch_sync(mainQueue, ^{
- [self updateImageWithData:data andIndex:i];
- });
- }
-
- #pragma mark 多线程下载图片
- -(void)loadImageWithMultiThread{
- int count=ROW_COUNT*COLUMN_COUNT;
-
-
- dispatch_queue_t serialQueue=dispatch_queue_create("myThreadQueue1", DISPATCH_QUEUE_SERIAL);
-
- for (int i=0; i<count; ++i) {
-
- dispatch_async(serialQueue, ^{
- [self loadImage:[NSNumber numberWithInt:i]];
- });
-
- }
-
- }
- @end
在上面的代码中更新UI还使用了GCD方法的主线程队列dispatch_get_main_queue(),其实这与前面两种主线程更新UI没有本质的区别。
并发队列
并发队列同样是使用dispatch_queue_create()方法创建,只是最后一个参数指定为DISPATCH_QUEUE_CONCURRENT进
行创建,但是在实际开发中我们通常不会重新创建一个并发队列而是使用dispatch_get_global_queue()方法取得一个全局的并发队列
(当然如果有多个并发队列可以使用前者创建)。下面通过并行队列演示一下多个图片的加载。代码与上面串行队列加载类似,只需要修改照片加载方法如下:
- -(void)loadImageWithMultiThread{
- int count=ROW_COUNT*COLUMN_COUNT;
-
-
- dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-
- for (int i=0; i<count; ++i) {
-
- dispatch_async(globalQueue, ^{
- [self loadImage:[NSNumber numberWithInt:i]];
- });
- }
- }
GCD执行任务的方法并非只有简单的同步调用方法和异步调用方法,还有其他一些常用方法:
- dispatch_apply():重复执行某个任务,但是注意这个方法没有办法异步执行(为了不阻塞线程可以使用dispatch_async()包装一下再执行)。
- dispatch_once():单次执行一个任务,此方法中的任务只会执行一次,重复调用也没办法重复执行(单例模式中常用此方法)。
- dispatch_time():延迟一定的时间后执行。
- dispatch_barrier_async():
使用此方法创建的任务首先会查看队列中有没有别的任务要执行,如果有,则会等待已有任务执行完毕再执行;同时在此方法后添加的任务必须等待此方法中任务执
行后才能执行。(利用这个方法可以控制执行顺序,例如前面先加载最后一张图片的需求就可以先使用这个方法将最后一张图片加载的操作添加到队列,然后调用
dispatch_async()添加其他图片加载任务)
- dispatch_group_async():实现对任务分组管理,如果一组任务全部完成可以通过dispatch_group_notify()方法获得完成通知(需要定义dispatch_group_t作为分组标识)。
iOS多线程开发--NSThread NSOperation GCD
标签:
原文地址:http://www.cnblogs.com/zhun/p/5515627.html