多线程(NSOperation和NSOperationQueue)
在网络应用程序中,经常要使用多任务处理来提高应用程序的性能,即在同一时间,有多个处理同时进行。例如,同时进行多个文件下载,同时进行多个HTTP 请求等。这一般都
是通过多线程完成的。另外,多线程编程也是为了防止主线程堵塞,增加运行效率的方法。比如,如果主线程从网上下载一个很大的图片,那么,给用户的感觉是整个应用程序死掉了。所以,可以把这个下载操作放在一个线程中,在主线程中调用这个线程让它在后台处理,主线程序可以显示一些其他信息,比如显示一些“正在装载”等文字信息。
在Cocoa中,NSOperation类提供了一个优秀的多线程编程方法。很多编程语言都支持多线程处理应用程序,但是多线程程序往往一旦出错就会很难处理。庆幸的是,苹果公司在这方面做了很多改进,例如在NSThread 上新增了很多方法,还新增了两个类NSOperation 和NSOperationQueue,从而让多线程处理变得更加容易。
在多线程中,可以同时进行多个操作。NSOperation 对象就是一个操作,比如,装载网页内容的一个操作。在Objective-C 上,一个具体的操作(比如网页装载)是一个继承NSOperation 的类。在这个类中,至少需要重写一个-(void)main 方法。线程(NSOperation)自动调用main 方法,main 方法就是线程要执行的具体操作。在下面的例子中,PageLoadOperation 继承了NSOperation,并实现了main 方法。一般而言,可以利用其初始化方法来传入所需要的参数和对象,比如PageLoadOperation的initWithURL:方法用来设置要装载的网址。
使用NSOperation 的最简单方法就是将其放入NSOperationQueue 中,NSOperationQueue是存放多个操作的队列。一旦一个NSOperation 对象被加入NSOperationQueue,该队列就会启动并开始处理它(即调用它的main方法),当操作完成后,队列就会释放它。
下面创建一个Cocoa Application例子来演示使用NSOperation和NSOperationQueue完成多线程处理。
应用代理类AppDelegate.h的代码如下:
- #import <Cocoa/Cocoa.h>
- @interface AppDelegate : NSObject {
- NSOperationQueue *queue; //线程队列
- }
- + (id)shared;
- - (void)pageLoaded:(NSXMLDocument*)document;
- @end
- AppDelegate.m的代码如下:
- #import "AppDelegate.h"
- #import "PageLoadOperation.h"
- @implementation AppDelegate
- static AppDelegate *shared;
- static NSArray *urlArray;
- - (id)init
- {
- if (shared) {
- [self autorelease];
- return shared;
- }
- if (![super init]) return nil;
- //设置要访问的网址
- NSMutableArray *array = [[NSMutableArray alloc] init];
- [array addObject:@"http://www.xinlaoshi.com"];
- [array addObject:@"http://www.yunwenjian.com"];
- [array addObject:@"http://www.108fang.com"];
- [array addObject:@"http://www.baidu.com"];
- urlArray = array;
- //[queue setMaxConcurrentOperationCount:2];
- queue = [[NSOperationQueue alloc] init];
- shared = self;
- return self;
- }
- - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
- { //把各个操作添加到队列中
- for (NSString *urlString in urlArray) {
- NSURL *url = [NSURL URLWithString:urlString];
- PageLoadOperation *plo = [[PageLoadOperation alloc]
- initWithURL:url];
- [queue addOperation:plo];
- [plo release];
- }
- }
- - (void)dealloc
- {
- [queue release], queue = nil;
- [super dealloc];
- }
- + (id)shared;
- {
- if (!shared) {
- [[AppDelegate alloc] init];
- }
- return shared;
- }
- //用于打印网页内容
- - (void)pageLoaded:(NSXMLDocument*)document;
- {
- NSLog(@"xml 文档是:%@", document);
- }
- @end
线程操作类PageLoadOperation.h的代码如下:
- #import <Cocoa/Cocoa.h>
- @interface PageLoadOperation : NSOperation {
- //需要使用多线程的类要继承NSOperation
- NSURL *targetURL;
- }
- @property(retain) NSURL *targetURL;
- - (id)initWithURL:(NSURL*)url;
- @end
- PageLoadOperation.m的代码如下:
- #import "PageLoadOperation.h"
- #import "AppDelegate.h"
- @implementation PageLoadOperation
- @synthesize targetURL;
- //获取要访问的网址
- - (id)initWithURL:(NSURL*)url;
- {
- if (![super init]) return nil;
- [self setTargetURL:url];
- return self;
- }
- - (void)dealloc {
- [targetURL release], targetURL = nil;
- [super dealloc];
- }
- //线程完成的操作。本例访问网址,并把该网址的内容放到一个NSXMLDocument 对象上
- - (void)main {
- //将targetURL 的值返回为webpageString 对象
- NSString *webpageString = [[[NSString alloc]initWithContentsOfURL:
- [self targetURL]] autorelease];
- NSError *error = nil;
- //访问网址,并把该网址的网页内容放到一个NSXMLDocument 对象上
- NSXMLDocument *document = [[NSXMLDocument alloc] initWithXMLString:
- webpageString options:NSXMLDocumentTidyHTML error:&error];
- if (!document) {
- //当document 为nil 的时候打印错误信息
- NSLog(@"错误信息:(%@): %@", [[self targetURL] absoluteString],
- error);
- return;
- }
- //拿到AppDelegate 对象并且调用主线程上的打印方法
- [[AppDelegate shared]
- performSelectorOnMainThread:@selector(pageLoaded:)
- withObject:document
- waitUntilDone:NO];
- }
- @end
【程序结果】
- xml 文档是:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
- Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-
- transitional.dtd"><html xmlns="
http://www.w3.org/1999/xhtml"><head><meta- http-equiv="Content-Type" content="
text/html; charset=utf-8" /><title>新老- 师-学网上课程 交圈内朋友</title>
- ....
- xml 文档是:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
- Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-
- transitional.dtd"><html><head><title>云文件</title>
- ....
- xml 文档是:<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0
- Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-
- transitional.dtd"><htmlxmlnshtmlxmlns="
http://www.w3.org/1999/xhtml"><head><meta- http-equiv="Content-Type" content="text/html; charset=utf-8" />
- <title>108 方手机应用平台</title>
- ....
- xml 文档是:<!doctype html><html><head><meta http-equiv="Content-Type"
- content="text/html;charset=gb2312"><title>百度一下,你就知道 </title>
下面分析一下这段程序执行过程,由于是一个Cocoa Application 应用程序,所以系统会先执行委托类AppDelegate 下的初始化方法- (id)init 进行一些初始化设置。在这个委托方法上,首先判断shared 是否已经初始化过。若初始化过,则直接返回结果;若是应用程序第一次调用初始化方法,则就初始化urlArray 数组,并将要用线程访问的各个网站地址装入其中,随后初始化shred 和NSOperationQueue 的对象queue。在应用装载结束以后,系统会调用另一个委托方法applicationDidFinishLaunching:方法,在这个方法中,我们遍历存入urlArray数组中的网站地址字符串,将其依次转换为NSURL对象。与此同时,创建同等数量的PageLoadOperation 对象,并将转换好的NSURL 对象设置为各个PageLoadOperation 对象的属性targetURL。我们还将初始化好的PageLoadOperation对象加入到queue队列上。
在队列中每加入一个线程操作后,队列都会为其分配一个NSThread 来启动它,并运行操作的main方法。一旦操作完成,线程就会报告给队列以让队列释放该操作。
在PageLoadOperation 类的main 方法上,根据前面设置好的targetURL 属性值,将该网址转换为字符串对象webpageString,并且加入自动释放池。利用转换好的webpageString 对象初始化NSXMLDocument对象,并访问这个网站,把内容放在NSXMLDocument对象上。如果在加载网站内容过程中发生错误,就会打印错误信息。如果没有错误,就调用主线程的pageLoaded 方法,从而打印网页内容到控制台上,然后任务结束。队列会在main 方法结束后自动释放该线程。
这个例子展示了NSOperation 和NSOperationQueue 最基本的使用。实例中的大部分代码与NSOperation 和NSOperationQueue 的设定和使用无关,都是一些业务实现代码。NSOperation本身所需要的代码非常少,但是通过这少量的代码就可以在应用中轻松地使用多线程,从而为用户提供更好的并发性能。另外,在init方法中,有一句代码:
- //[queue setMaxConcurrentOperationCount:2];
这是用来设置线程的个数。如果去掉上面的注释,那么,线程队列就被限制为只能同时运行两个操作,剩余的操作就需要等待这两个操作中的任一个完成后才有可能被运行。