原文地址http://www.cimgf.com/2008/02/16/cocoa-tutorial-nsoperation-and-nsoperationqueue/
在任何一种语言里,线程都是一个难点,更糟糕的是,如果线程出了问题,往往会以一种非常糟糕的方式出现。因为这个,程序员要么竭力避免线程编程(将线程看作是魔鬼的种子),要么花费大量时间去确保所有线程代码都运行良好。
幸运的是,Apple在OS X 10.5 leopard 有了很大的进步。NSThread类添加了很多非常有用的新方法,这些方法都使线程编程变得更加简单。另外,Apple引入了两个新的对象:NSOperation和NSOperationQueue。在这篇教程里,我将剖析一个简单的例子来演示怎么来用这些新对象,以及他们怎样在你的应用里加入多线程。
这里可以得到例子工程: Async Downloader Example Project
这篇教程,我将演示其中一种用NSOperation和NSOperationQueue处理一些适合后台运行的任务的方法,这篇教程的目的是演示这些类的基本使用方法,但不是说只有这种唯一的方法使用他们。
如果你对java,或者它的变种熟悉的话,那么可以说,NSOperation 对象很像java.lang.Runnable接口。类似的,在java的Runnable接口中,NSOperation对象被设计为可以扩展的。还是和java一样,这里也有一个方法可以被重载次数的最小值。对于NSOpetation来说,这个方法就是-(void)main.使用NSOperation的最简单的一种方法是把它加入一个NSOperationQueue。一旦operation被加入了这个队列,队列就马上把它踢出开始运行。operation运行结束后,队列就马上将它释放。
NSOperation 例子
在这个例子里,我写了一个NSOperation,在NSOperation里得到一个网页,将这个网页内容保存到一个字符串里,再把它解析到一个NSXMLDocument,然后再完成前,再把NSXMLDocument传回到应用程序的主线程。
PageLoadOperation.h
#import <Cocoa/Cocoa.h>
@interface PageLoadOperation : 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];
}
- (void)main {
NSString *webpageString = [[[NSString alloc] initWithContentsOfURL:[self targetURL]] autorelease];
NSError *error = nil;
NSXMLDocument *document = [[NSXMLDocument alloc] initWithXMLString:webpageString
options:NSXMLDocumentTidyHTML
error:&error];
if (!document) {
NSLog(@"%s Error loading document (%@): %@", _cmd, [[self targetURL] absoluteString], error);
return;
}
[[AppDelegate shared] performSelectorOnMainThread:@selector(pageLoaded:)
withObject:document
waitUntilDone:YES];
[document release];
}
@end
就像你看到的,这个类很简单。在初始化方法里接受一个URL,并且保存起来。当main方法被调用的时候,用URL构造一个字符串,然后将这个字符串传到NSXMLDocument的init方法。如果加载xml文档没有出错,就把它传回到AppDelegate,在主线程里,任务完成。当NSOperation的main方法结束,队列将自动释放对象。
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.google.com"];
[array addObject:@"http://www.apple.com"];
[array addObject:@"http://www.yahoo.com"];
[array addObject:@"http://www.zarrastudios.com"];
[array addObject:@"http://www.macosxhints.com"];
urlArray = array;
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(@"%s Do something with the XMLDocument: %@", _cmd, document);
}
@end
在这个示例AppDelegate里,有两件事发生。第一,在init方法里,NSOperationQueue被初始化,并且有一组url被加载。然后,当应用程序完成了加载,applicationDidFinishLaunching:方法被NSApplication实例调用,AppDelegate在url上循环,为每一个url创建一个任务,并且把这些任务加入NSOperationQueue,一旦每一项加入了队列,队列就马上弹出它赋给一个NSThread,线程将会运行opreation的main方法。一旦operation完成了,线程就马上通知队列,队列就会释放这个operation。
NSOperationQueue 同步
在这个非常简单的例子里,加载足够的对象头查看他们同步运行事十分困难的。但是如果你执行的任务需要比这个花费更多的时间,你就会看到,队列将同时运行所用的任务。幸运的是,如果你想设定同时可以运行的任务数量,你可以修改AppDelegate里的init方法:
- (id)init
{
if (shared) {
[self autorelease];
return shared;
}
if (![super init]) return nil;
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:@"http://www.google.com"];
[array addObject:@"http://www.apple.com"];
[array addObject:@"http://www.yahoo.com"];
[array addObject:@"http://www.zarrastudios.com"];
[array addObject:@"http://www.macosxhints.com"];
urlArray = array;
queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:2];
shared = self;
return self;
}
在这个修改后的init方法里,队列被限制到每次只有两个操作可以同时运行。剩下的操作将不断等待,直到前面的两个操作完成,它们才有机会去运行。
结论
上面是NSOperation 和 NSOperationQueue使用中的基本形式。你会注意到,这个例子中的大部分代码与创建和使用NSOperation 或者 NSOperationQueue毫无关系。实际编码中需要用到的NSOperation很少,但是只要用这少量的代码你就可以轻松的开始在你的应用程序中使用多线程,不但可以给用户提供一份更好的体验,而且可以更好的解决复杂的任务。