博客> 多线程(2)-- NSThread
多线程(2)-- NSThread
2017-11-22 02:33 评论:0 阅读:163 lvhahaha
多线程

NSThread是轻量级的多线程开发,使用起来也并不复杂,但是使用NSThread需要自己管理线程生命周期。可以使用对象方法+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument直接将操作添加到线程中并启动,也可以使用对象方法- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 创建一个线程对象,然后调用start方法启动线程。

解决线程阻塞问题 在资源下载过程中,由于网络原因有时候很难保证下载时间,如果不使用多线程可能用户完成一个下载操作需要长时间的等待,这个过程中无法进行其他操作。下面演示一个采用多线程下载图片的过程,在这个示例中点击按钮会启动一个线程去下载图片,下载完成后使用UIImageView将图片显示到界面中。可以看到用户点击完下载按钮后,不管图片是否下载完成都可以继续操作界面,不会造成阻塞。

// // NSThread实现多线程 // MultiThread // // Created by Kenshin Cui on 14-3-22. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. //

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{ 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]; /将数据显示到UI控件,注意只能在主线程中更新UI, 另外performSelectorOnMainThread方法是NSObject的分类方法,每个NSObject对象都有此方法, 它调用的selector方法是当前调用控件的方法,例如使用UIImageView调用的时候selector就是UIImageView的方法 Object:代表调用方法的参数,不过只能传递一个参数(如果有多个参数请使用对象进行封装) waitUntilDone:是否线程任务完成执行 */ [self performSelectorOnMainThread:@selector(updateImage:) withObject:data waitUntilDone:YES]; }

pragma mark 多线程下载图片

-(void)loadImageWithMultiThread{ //方法1:使用对象方法 //创建一个线程,第一个参数是请求的操作,第二个参数是操作方法的参数 // NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage) object:nil]; // //启动一个线程,注意启动一个线程并非就一定立即执行,而是处于就绪状态,当系统调度时才真正执行 // [thread start];

//方法2:使用类方法
[NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil];

} @end 运行效果:

NSThreadEffect

程序比较简单,但是需要注意执行步骤:当点击了“加载图片”按钮后启动一个新的线程,这个线程在演示中大概用了5s左右,在这5s内UI线程是不会阻塞的,用户可以进行其他操作,大约5s之后图片下载完成,此时调用UI线程将图片显示到界面中(这个过程瞬间完成)。另外前面也提到过,更新UI的时候使用UI线程,这里调用了NSObject的分类扩展方法,调用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

// // KCImageData.h // MultiThread // // Created by Kenshin Cui on 14-3-22. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. //

import

@interface KCImageData : NSObject

pragma mark 索引

@property (nonatomic,assign) int index;

pragma mark 图片数据

@property (nonatomic,strong) NSData *data;

@end 接下来将创建多个UIImageView并创建多个线程用于往UIImageView中填充图片。

KCMainViewController.m

// // NSThread实现多线程 // MultiThread // // Created by Kenshin Cui on 14-3-22. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. //

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(cROW_WIDTH+(cCELL_SPACING), rROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)]; imageView.contentMode=UIViewContentModeScaleAspectFit; // imageView.backgroundColor=[UIColor redColor]; [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{ 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(@"%i",i); //currentThread方法可以取得当前操作线程 NSLog(@"current thread:%@",[NSThread currentThread]);

int i=[index integerValue];

// NSLog(@"%i",i);//未必按顺序输出

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_COUNTCOLUMN_COUNT; ++i) { // [NSThread detachNewThreadSelector:@selector(loadImage:) toTarget:self withObject:[NSNumber numberWithInt:i]]; NSThread thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:[NSNumber numberWithInt:i]]; thread.name=[NSString stringWithFormat:@"myThread%i",i];//设置线程名称 [thread start]; } } @end NSThreadEffect2

通过NSThread的currentThread可以取得当前操作的线程,其中会记录线程名称name和编号number,需要注意主线程编号永远为1。多个线程虽然按顺序启动,但是实际执行未必按照顺序加载照片(loadImage:方法未必依次创建,可以通过在loadImage:中打印索引查看),因为线程启动后仅仅处于就绪状态,实际是否执行要由CPU根据当前状态调度。

从上面的运行效果大家不难发现,图片并未按顺序加载,原因有两个:第一,每个线程的实际执行顺序并不一定按顺序执行(虽然是按顺序启动);第二,每个线程执行时实际网络状况很可能不一致。当然网络问题无法改变,只能尽可能让网速更快,但是可以改变线程的优先级,让15个线程优先执行某个线程。线程优先级范围为0~1,值越大优先级越高,每个线程的优先级默认为0.5。修改图片下载方法如下,改变最后一张图片加载的优先级,这样可以提高它被优先加载的几率,但是它也未必就第一个加载。因为首先其他线程是先启动的,其次网络状况我们没办法修改:

-(void)loadImageWithMultiThread{ NSMutableArray threads=[NSMutableArray array]; int count=ROW_COUNTCOLUMN_COUNT; //创建多个线程用于填充图片 for (int i=0; i<count; ++i) { // [NSThread detachNewThreadSelector:@selector(loadImage:) toTarget:self withObject:[NSNumber numberWithInt:i]]; NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:[NSNumber numberWithInt:i]]; thread.name=[NSString stringWithFormat:@"myThread%i",i];//设置线程名称 if(i==(count-1)){ thread.threadPriority=1.0; }else{ thread.threadPriority=0.0; } [threads addObject:thread]; }

for (int i=0; i&lt;count; i++) {
    NSThread *thread=threads[i];
    [thread start];
}

} 线程状态 在线程操作过程中可以让某个线程休眠等待,优先执行其他线程操作,而且在这个过程中还可以修改某个线程的状态或者终止某个指定线程。为了解决上面优先加载最后一张图片的问题,不妨让其他线程先休眠一会等待最后一个线程执行。修改图片加载方法如下即可:

-(NSData )requestData:(int )index{ //对非最后一张图片加载线程休眠2秒 if (index!=(ROW_COUNTCOLUMN_COUNT-1)) { [NSThread sleepForTimeInterval:2.0]; } NSURL url=[NSURL URLWithString:_imageNames[index]]; NSData data=[NSData dataWithContentsOfURL:url];

return data;

} 在这里让其他线程休眠2秒,此时你就会看到最后一张图片总是第一个加载(除非网速特别差)。 线程状态分为isExecuting(正在执行)、isFinished(已经完成)、isCancellled(已经取消)三种。其中取消状态程序可以干预设置,只要调用线程的cancel方法即可。但是需要注意在主线程中仅仅能设置线程状态,并不能真正停止当前线程,如果要终止线程必须在线程中调用exist方法,这是一个静态方法,调用该方法可以退出当前线程。

假设在图片加载过程中点击停止按钮让没有完成的线程停止加载,可以改造程序如下:

// // NSThread实现多线程 // MultiThread // // Created by Kenshin Cui on 14-3-22. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. //

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; NSMutableArray *_threads; }

@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(cROW_WIDTH+(cCELL_SPACING), rROW_HEIGHT+(r*CELL_SPACING ), ROW_WIDTH, ROW_HEIGHT)]; imageView.contentMode=UIViewContentModeScaleAspectFit; // imageView.backgroundColor=[UIColor redColor]; [self.view addSubview:imageView]; [_imageViews addObject:imageView];

    }
}

//加载按钮
UIButton *buttonStart=[UIButton buttonWithType:UIButtonTypeRoundedRect];
buttonStart.frame=CGRectMake(50, 500, 100, 25);
[buttonStart setTitle:@"加载图片" forState:UIControlStateNormal];
[buttonStart addTarget:self action:@selector(loadImageWithMultiThread) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:buttonStart];

//停止按钮
UIButton *buttonStop=[UIButton buttonWithType:UIButtonTypeRoundedRect];
buttonStop.frame=CGRectMake(160, 500, 100, 25);
[buttonStop setTitle:@"停止加载" forState:UIControlStateNormal];
[buttonStop addTarget:self action:@selector(stopLoadImage) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:buttonStop];

//创建图片链接
_imageNames=[NSMutableArray array];
[_imageNames addObject:@    for (int i=0; i&lt;IMAGE_COUNT; i++) {
    [_imageNames addObject:[NSString stringWithFormat:@"http://images.cnblogs.com/cnblogs_com/kenshincui/613474/o_%i.jpg",i]];
}    

}

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{ NSURL url=[NSURL URLWithString:_imageNames[index]]; NSData *data=[NSData dataWithContentsOfURL:url];

return data;

}

pragma mark 加载图片

-(void)loadImage:(NSNumber *)index{ int i=[index integerValue];

NSData *data= [self requestData:i];

NSThread *currentThread=[NSThread currentThread];

// 如果当前线程处于取消状态,则退出当前线程 if (currentThread.isCancelled) { NSLog(@"thread(%@) will be cancelled!",currentThread); [NSThread exit];//取消当前线程 }

KCImageData *imageData=[[KCImageData alloc]init];
imageData.index=i;
imageData.data=data;
[self performSelectorOnMainThread:@selector(updateImage:) withObject:imageData waitUntilDone:YES];

}

pragma mark 多线程下载图片

-(void)loadImageWithMultiThread{ int count=ROW_COUNT*COLUMN_COUNT; _threads=[NSMutableArray arrayWithCapacity:count];

//创建多个线程用于填充图片
for (int i=0; i&lt;count; ++i) {
    NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage:) object:[NSNumber numberWithInt:i]];
    thread.name=[NSString stringWithFormat:@"myThread%i",i];//设置线程名称
    [_threads addObject:thread];
}
//循环启动线程
for (int i=0; i&lt;count; ++i) {
    NSThread *thread= _threads[i];
    [thread start];
}

}

pragma mark 停止加载图片

-(void)stopLoadImage{ for (int i=0; i<ROW_COUNTCOLUMN_COUNT; i++) { NSThread thread= _threads[i]; //判断线程是否完成,如果没有完成则设置为取消状态 //注意设置为取消状态仅仅是改变了线程状态而言,并不能终止线程 if (!thread.isFinished) { [thread cancel];

    }
}

} @end 运行效果(点击加载大概1秒后点击停止加载):

NSThreadEffect3

使用NSThread在进行多线程开发过程中操作比较简单,但是要控制线程执行顺序并不容易(前面万不得已采用了休眠的方法),另外在这个过程中如果打印线程会发现循环几次就创建了几个线程,这在实际开发过程中是不得不考虑的问题,因为每个线程的创建也是相当占用系统开销的。

扩展--NSObject分类扩展方法 为了简化多线程开发过程,苹果官方对NSObject进行分类扩展(本质还是创建NSThread),对于简单的多线程操作可以直接使用这些扩展方法。

  • (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg:在后台执行一个操作,本质就是重新创建一个线程执行当前方法。

  • (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait:在指定的线程上执行一个方法,需要用户创建一个线程对象。

  • (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait:在主线程上执行一个方法(前面已经使用过)。

例如前面加载图多个图片的方法,可以改为后台线程执行:

-(void)loadImageWithMultiThread{ int count=ROW_COUNT*COLUMN_COUNT;

for (int i=0; i&lt;count; ++i) {
    [self performSelectorInBackground:@selector(loadImage:) withObject:[NSNumber numberWithInt:i]];
}

} http://www.cnblogs.com/kenshincui/p/3983982.html

收藏
0
sina weixin mail 回到顶部