Cocoa:NSOperation和NSOperationQueue
Cocoa: NSOperation和NSOperationQueue
在任何语言中多线程处理都是麻烦的。更糟糕的是如果出错了往往会以很坏的方式出错。鉴于此,程序员要么完全避免使用多线程(把它当做邪恶之源),要么发很长的时间来确保每个方面都很完美。
庆幸的是,Apple在OS X 10.5 Leopard上做了很多改进。NSThread本身就新增了很多新的方法,从而使得多线程变得更加容易。此外还新增了NSOperation和NSOperationQueue两个类。该教程通过一个简单的实例来介绍如何使用这些新增类并如何让多线程的应用变得小菜一碟。
你可以从此获取该项目的实例代码: Async Downloader Example Project
在该教程中,我会介绍一种使用NSOperation和NSOperationQueue在后台线程中很好地处理任务的方法。该教程的目标是介绍这些类的使用,但并不排除使用它们的其他方法。
如果你熟悉Java或一个它的变种语言,NSOperation就和java.lang.Runnable接口很相似。和Java的Runnable一样,NSOperation也是设计用来扩展的,并且最低仅需重写一个方法。对于NSOperation这个方法是-(void)main。一个使用NSOperation的最简单方法就是将其放入NSOperationQueue中。一旦一个操作被加入队列,该队列就会启动并开始处理它。一旦该操作完成队列就会释放它。
除了通过继承NSOperation以外,还可以通过 NSBlockOperation 直接创建一个operation对象,线程中调用block里的方法。
1 NSOperation* operation = [NSBlockOperation blockOperationWithBlock:^{ 2 //....... 3 }];
NSOperation实例
在该实例中,我编写了一段获取网页内容的字符串,然后解析成NSXMLDocument对象并在结束前传回应用主线程。
PageLoadOperation.h
1 #import <Cocoa/Cocoa.h> 2 @interface PageLoadOperation : NSOperation { 3 NSURL *targetURL; 4 } 5 6 @property(retain) NSURL *targetURL; 7 8 - (id)initWithURL:(NSURL*)url; 9 10 @end
PageLoadOperation.m
1 #import "PageLoadOperation.h" 2 #import "AppDelegate.h" 3 4 @implementation PageLoadOperation 5 6 @synthesize targetURL; 7 8 - (id)initWithURL:(NSURL*)url; 9 { 10 if (![super init]) return nil; 11 [self setTargetURL:url]; 12 return self; 13 } 14 15 - (void)dealloc { 16 [targetURL release], targetURL = nil; 17 [super dealloc]; 18 } 19 20 - (void)main { 21 NSString *webpageString = [[[NSString alloc] initWithContentsOfURL:[self targetURL]] autorelease]; 22 23 NSError *error = nil; 24 NSXMLDocument *document = [[NSXMLDocument alloc] initWithXMLString:webpageString 25 options:NSXMLDocumentTidyHTML 26 error:&error]; 27 if (!document) { 28 NSLog(@"%s Error loading document (%@): %@", _cmd, [[self targetURL] absoluteString], error); 29 return; 30 } 31 32 [[AppDelegate shared] performSelectorOnMainThread:@selector(pageLoaded:) 33 withObject:document 34 waitUntilDone:YES]; 35 [document release]; 36 } 37 38 @end
我们可以看到,这个类很简单。它在init方法中接受一个URL并保存起来。在调用main方法的时候它就从URL中构建一个字符串并传给NSXMLDocument的init方法。如果在加载xml文档过程中没有发生错误,该文档就会被传回到主线程的AppDelegate,然后任务结束。队列会在NSOperation的main方法结束后自动释放该对象。
AppDelegate.h
1 #import <Cocoa/Cocoa.h> 2 3 @interface AppDelegate : NSObject { 4 NSOperationQueue *queue; 5 } 6 7 + (id)shared; 8 - (void)pageLoaded:(NSXMLDocument*)document; 9 10 @end
AppDelegate.m
1 #import "AppDelegate.h" 2 #import "PageLoadOperation.h" 3 4 @implementation AppDelegate 5 static AppDelegate *shared; 6 static NSArray *urlArray; 7 8 - (id)init 9 { 10 if (shared) { 11 [self autorelease]; 12 return shared; 13 } 14 if (![super init]) return nil; 15 16 NSMutableArray *array = [[NSMutableArray alloc] init]; 17 [array addObject:@"http://www.google.com"]; 18 [array addObject:@"http://www.apple.com"]; 19 [array addObject:@"http://www.yahoo.com"]; 20 [array addObject:@"http://www.zarrastudios.com"]; 21 [array addObject:@"http://www.macosxhints.com"]; 22 urlArray = array; 23 24 queue = [[NSOperationQueue alloc] init]; 25 shared = self; 26 return self; 27 } 28 29 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification 30 { 31 for (NSString *urlString in urlArray) { 32 NSURL *url = [NSURL URLWithString:urlString]; 33 PageLoadOperation *plo = [[PageLoadOperation alloc] initWithURL:url]; 34 [queue addOperation:plo]; 35 [plo release]; 36 } 37 } 38 39 - (void)dealloc 40 { 41 [queue release], queue = nil; 42 [super dealloc]; 43 } 44 45 + (id)shared; 46 { 47 if (!shared) { 48 [[AppDelegate alloc] init]; 49 } 50 return shared; 51 } 52 53 - (void)pageLoaded:(NSXMLDocument*)document; 54 { 55 NSLog(@"%s Do something with the XMLDocument: %@", _cmd, document); 56 } 57 58 @end
在实例中AppDelegate做了两件事。其一是在init方法中初始化了NSOperationQueue并载入了一个urls数组。在应用结束加载时NSApplication 实例会调用applicationDidFinishLaunching:方法,AppDelegate就遍历每个url并创建相应的任务,然后将任务加入队列中。在队列中每加入一个人后,队列都会为其分配一个NSThread来启动它,线程就会运行操作的main方法。一旦操作完成,线程就会报告给队列以让队列释放该操作。
NSOperationQueue的并发
在这个简单的实例中,由于很难再队列中加入足够多的任务使得我们很难看到它们是并发运行的。但如果你的任务需要更多的时间,你就可以看到队列是同时运行所有的任务。如果你想限制并行运行的任务数目,你可以在AppDelegate的init方法中做如下修改。
1 - (id)init 2 { 3 if (shared) { 4 [self autorelease]; 5 return shared; 6 } 7 if (![super init]) return nil; 8 9 NSMutableArray *array = [[NSMutableArray alloc] init]; 10 [array addObject:@"http://www.google.com"]; 11 [array addObject:@"http://www.apple.com"]; 12 [array addObject:@"http://www.yahoo.com"]; 13 [array addObject:@"http://www.zarrastudios.com"]; 14 [array addObject:@"http://www.macosxhints.com"]; 15 urlArray = array; 16 queue = [[NSOperationQueue alloc] init]; 17 [queue setMaxConcurrentOperationCount:2]; 18 shared = self; 19 return self; 20 }
在这个修改的init方法中,队列被限制只能同时运行两个操作。剩余的操作就需要等待之前的两个操作有一个完成后才有机会被运行。
结论
这是NSOperation和NSOperationQueue最基本的使用。你可以注意到该实例的大部分代码和NSOperation和NSOperationQueue 的设定和使用无关。NSOperation所需要的代码是惊人地少。但是通过这少量的代码你就可以在你的应用中轻松地使用多线程以为用户提供更好的用户体验并可以更好地优化复杂任务。
原文链接:http://www.cimgf.com/2008/02/16/cocoa-tutorial-nsoperation-and-nsoperationqueue/