|
原文地址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 AppDelegatestatic 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很少,但是只要用這少量的代碼你就可以輕鬆的開始在你的應用程式中使用多線程,不但可以給使用者提供一份更好的體驗,而且可以更好的解決複雜的任務。