简介
当用户播放音频、下载资源、进行图像处理时往往希望做这些事情的时候其他操作不会被中断或者希望这些操作过程中更加顺畅。在单线程中一个线程只能做一件事情,一件事情处理不完另一件事就不能开始,这样势必影响用户体验。早在单核处理器时期就有多线程,这个时候多线程更多的用于解决线程阻塞造成的用户等待(通常是操作完UI后用户不再干涉,其他线程在等待队列中,CPU一旦空闲就继续执行,不影响用户其他UI操作),其处理能力并没有明显的变化。如今无论是移动操作系统还是PC、服务器都是多核处理器,于是“并行运算”就更多的被提及。一件事情我们可以分成多个步骤,在没有顺序要求的情况下使用多线程既能解决线程阻塞又能充分利用多核处理器运行能力。
下图反映了一个包含8个操作的任务在一个有两核心的CPU中创建四个线程运行的情况。假设每个核心有两个线程,那么每个CPU中两个线程会交替执行,两个CPU之间的操作会并行运算。单就一个CPU而言两个线程可以解决线程阻塞造成的不流畅问题,其本身运行效率并没有提高,多CPU的并行运算才真正解决了运行效率问题,这也正是并发和并行的区别。当然,不管是多核还是单核开发人员不用过多的担心,因为任务具体分配给几个CPU运算是由系统调度的,开发人员不用过多关心系统有几个CPU。开发人员需要关心的是线程之间的依赖关系,因为有些操作必须在某个操作完成完才能执行,如果不能保证这个顺序势必会造成程序问题。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
iOS多线程
在iOS中每个进程启动后都会建立一个主线程(UI线程),这个线程是其他线程的父线程。由于在iOS中除了主线程,其他子线程是独立于Cocoa Touch的,所以只有主线程可以更新UI界面(新版iOS中,使用其他线程更新UI可能也能成功,但是不推荐)。iOS中多线程使用并不复杂,关键是如何控制好各个线程的执行顺序、处理好资源竞争问题。常用的多线程开发有三种方式:
1.NSThread
2.NSOperation
3.GCD
三种方式是随着iOS的发展逐渐引入的,所以相比而言后者比前者更加简单易用,并且GCD也是目前苹果官方比较推荐的方式(它充分利用了多核处理器的运算性能)。做过.Net开发的朋友不难发现其实这三种开发方式 刚好对应.Net中的多线程、线程池和异步调用,因此在文章中也会对比讲解。
NSThread
NSThread是轻量级的多线程开发,使用起来也并不复杂,但是使用NSThread需要自己管理线程生命周期。可以使用对象方法+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument直接将操作添加到线程中并启动,也可以使用对象方法- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 创建一个线程对象,然后调用start方法启动线程。
解决线程阻塞问题
在资源下载过程中,由于网络原因有时候很难保证下载时间,如果不使用多线程可能用户完成一个下载操作需要长时间的等待,这个过程中无法进行其他操作。下面演示一个采用多线程下载图片的过程,在这个示例中点击按钮会启动一个线程去下载图片,下载完成后使用UIImageView将图片显示到界面中。可以看到用户点击完下载按钮后,不管图片是否下载完成都可以继续操作界面,不会造成阻塞。
#import "ViewController.h"
@interface ViewController (){
UIImageView * _imageView;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[ self setImageWithView];
}
#pragma mark -界面布局
-(void)setImageWithView{
//设置图片框的大小
_imageView = [[ UIImageView alloc]initWithFrame:[ UIScreen mainScreen].bounds];
_imageView.contentMode = UIViewContentModeScaleAspectFit;
//把图片框添加到控制器中
[self.view addSubview:_imageView];
//添加一个按钮 为按钮注册一个点击事件
UIButton * button = [ UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(50, 500, 250, 25);
//设置按钮的文字
[button setTitle:@"加载图片" forState:UIControlStateNormal];
//为按钮注册一个单击事件
[ button addTarget:self action:@selector(loadImageWithThread) forControlEvents:UIControlEventTouchUpInside];
//把按钮添加到控制器中
[ self.view addSubview:button];
}
#pragma mark - 创建子线程
-(void)loadImageWithThread{
//类方法 不用手动管理线程
[NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil];
}
#pragma mark -加载图片
-(void)loadImage{
//获取图片路径
NSURL * url = [ NSURL URLWithString:@"http://pic2.ooopic.com/01/03/51/25b1OOOPIC19.jpg"];
//把路径转换成二进制文件
NSData * data = [ NSData dataWithContentsOfURL:url];
UIImage * image = [ UIImage imageWithData:data ];
//利用主线程的方法把图片传到主线程去 waitUntilDone:是否线程任务完成执行
[ self performSelectorOnMainThread:@selector(setImageToMainThread:) withObject:image waitUntilDone:NO];
// return image;
}
#pragma mark - 把加载好的图片传 到主线程中
-(void)setImageToMainThread:(UIImage *)image{
_imageView.image = image;
}
@end
运行之前请先修改下info.plist文件
左边添加一个App Transport Security Settings 的属性 ,在App Transport Security Settings下再次添加一个Allow Arbitrary Loads 把Allow Arbitrary Loads的右边改为YES ,亲就可以看到你期待的图片咯!
请注意:被访问的网址不能带有特殊符号(如&)和中文,
程序比较简单,但是需要注意执行步骤:当点击了“加载图片”按钮后启动一个新的线程,这个线程在演示中大概用了5s左右,在这5s内UI线程是不会阻塞的,用户可以进行其他操作,大约5s之后图片下载完成,此时调用UI线程将图片显示到界面中(这个过程瞬间完成)。另外前面也提到过,更新UI的时候使用UI线程,这里调用了NSObject的分类扩展方法,调用UI线程完成更新。
目前只学到这 ,下面的GCD和NSOperation过段时间再上传。