多线程这个概念的接触是蛮早的时候了,当时还是单核单CPU的时候,Thread这个概念已经出现了,当时比较流行的方案是时间片轮流,线程可以优先级抢占,但一次只能运行一个线程,实际上多线程是不能真正并行处理的,只是宏观上表现的多线程在齐头并进。现在硬件进步了很多,多核的CPU时代来临了,于是线程开始了真正意义上的并行处理,多线程也作为越来越重要的一个部分需要掌握。
iOS中关于线程的创建和运行,提供了3种方法:NSThread,NSOperation和GCD。这三种方式抽象程度越来越高,所以编写代码是越来越简单的。
我们先来看NSThread吧。
NSThread比其他两个都要更轻量级,但需要自己来管理线程的生命周期和同步,代码的编写上比其他两个复杂。对于线程的技术,苹果的官方文档上给出了一张表:
Technology |
Description |
---|---|
Cocoa threads |
Cocoa implements threads using the |
POSIX threads |
POSIX threads provide a C-based interface for creating threads. If you are not writing a Cocoa application, this is the best choice for creating threads. The POSIX interface is relatively simple to use and offers ample flexibility for configuring your threads. For more information, see “Using POSIX Threads” |
Multiprocessing Services |
Multiprocessing Services is a legacy C-based interface used by applications transitioning from older versions of Mac OS. This technology is available in OS X only and should be avoided for any new development. Instead, you should use the |
苹果虽然支持3种技术,但我们实际中最常用的也就是第一种,使用NSThread来进行线程的控制。NSThread有2种初始化方式,一种是传统的init方式(initWithTarget:selector:object:),另一种是类方法(+ detachNewThreadSelector:toTarget:withObject:)
initWithTarget方法用法如下:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImage:) object:IMAGE_URL]; [thread start];
这个里面的@selector就是线程的入口点,只能接受一个传入参数,并且没有返回值。还有一个需要强调一下,init仅仅是创建了线程,要线程运行还需要调用start方法。
如果要换成detachNewThreadSelector的类方法,用法如下:
[NSThread detachNewThreadSelector:@selector(downloadImage:) toTarget:self withObject:IMAGE_URL];
我们还是看代码吧,这个代码很简单,是实现后台下载,并在一张UIImage上显示。
用NSThread的方法,采用initWithTarget的做法:
#import "ViewController.h" #define IMAGE_URL @"http://williamzhang-public.qiniudn.com/DSC_0069.jpg" @interface ViewController () @end @implementation ViewController @synthesize imageView; - (void)downloadImage:(NSString*)url { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]]; UIImage *image = [[UIImage alloc] initWithData:data]; if (image) { [self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES]; } [pool drain]; } - (void)updateUI:(UIImage*)image { self.imageView.image = image; } - (void)viewDidLoad { [super viewDidLoad]; NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImage:) object:IMAGE_URL]; [thread start]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)dealloc { [imageView release]; [super dealloc]; } @end
在viewDidLoad中,我们生成一个线程并运行,线程的入口点就是downloadImage:方法,用来下载图片,并通知主线程刷新界面。
如果采用detachNewThreadSelector方法就是把viewDidLoad方法稍微改改就行:
- (void)viewDidLoad { [super viewDidLoad]; [NSThread detachNewThreadSelector:@selector(downloadImage:) toTarget:self withObject:IMAGE_URL]; }
这就是NSObject类提供的performSelectorInBackground:withObject:方法和performSelectorOnMainThread:withObject:waitUntilDone:方法。
在上面的例子中已经使用了performSelectorOnMainThread:withObject:waitUntilDone:方法,在下载图片后就是用这个方法让主线程刷新UI的。
- (void)viewDidLoad { [super viewDidLoad]; [self performSelectorInBackground:@selector(downloadImage:) withObject:IMAGE_URL]; }如果这样写的话,虽然也是本质还是多线程,但你根本感觉不到NSThread的存在,可以简单的理解就运行一个后台的任务,在不少场合还是更简单清晰一些。
最后要说的一个是生成的线程,对内存的管理也是要自己负责的,所以需要生成autorelease pool,如果你开启了ARC功能,那么可以简单的用@autoreleasepool这个关键字。