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实例

在该实例中,我编写了一段获取网页内容的字符串,然后解析成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

我们可以看到,这个类很简单。它在init方法中接受一个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并载入了一个urls数组。在应用结束加载时NSApplication 实例会调用applicationDidFinishLaunching:方法,AppDelegate就遍历每个url并创建相应的任务,然后将任务加 入队列中。在队列中每加入一个人后,队列都会为其分配一个NSThread来启动它,线程就会运行操作的main方法。一旦操作完成,线程就会报告给队列 以让队列释放该操作。
 
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所需要的代码是惊人地少。但是通过这少量的代码你就可以在你的应用中轻松地使用多线程以为用户提供更好的用户体 验并可以更好地优化复杂任务。
原文链接:http://www.cimgf.com/2008/02/16/cocoa-tutorial-nsoperation-and-nsoperationqueue/