NSOperation和NSOperationQueue


最近在网上闲逛,看到一个老外的博客,感觉里面iphone开发的文章相当不错,自己就暂且选了一篇进行翻译,以备日后使用。这里有这个博客的链接,有兴趣的可以看下博客。下面是Cocoa Tutorial: NSOperation and NSOperationQueue这篇博文的原文翻译。

在任何语言中线程都是困难的。更糟糕的是,当线程出错的时候,会出现意想不到的情况。因为这些,程序员要么完全的避免线程,要么就花费大量的时间确保线程万无一失。

值得庆幸的是,苹果在狮子系统中对此做了大量的改进。利用NSThread封装了大量的方法,以便于线程容易管理。另外,苹果介绍了两个新的对象:NSOperation和NSOperationQueue。这个教程,我将利用一些简单的例子,展示如何使用这些新对象和怎么使多线程应用程序顺利执行。

下面的网址你可以获得工程的例子 在这个教程中,我将展示利用NSOperation和NSOperationQueue怎么来控制后台的线程。这个教程的目的仅仅是用来演示这些类怎么使用的,所以并不是使用这些类的唯一方法。

NSOperation对象

如果你熟悉java语言的话。NSOperation对象很像java.lang.Runnable这个接口。就像java.lang.Runnable一样,NSOperation对象也被设计成可以扩展的。在java的Runnable中,有一个最低级的重载的方法。在NSOperation中此方法是 -(void)main。使用NSOperation最简单的方法是放置它到NSOperationQueue中。只要该操作被导入到队列中,此队列就立刻取得该操作,然后执行它。然后此操作完成,队列就释放它。 这个例子中,我写了一个NSOperation来抓取一个网页的字符串,然后把这些字符串放到NSXMLDocument中解析,然后在此操作完成之前,传递NSXMLDocument对象给主线程。

PageLoadOperation.h

#import <Cocoa/Cocoa.h>
 
 
@interface PageLoadOperation : NSOperation {
    NSURL *targetURL;
}
 
@property(retain) NSURL *targetURL;
 
- (id)initWithURL:(NSURL*)url;
 
@end 
#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,并且存储了这个URL。当main方法被调用时,它从URL中构造了一个字符串,然后传递这个字符串传给NSXMLDocument初始化。假如在装载xml文档的时候,没有错误发生,它将回传给AppDelegate,在主线程上,然后此任务完成。当main方法结束时,NSOperation也会在队列中被释放。

AppDelegate.h
#import <Cocoa/Cocoa.h>
 
@interface AppDelegate : NSObject {
	NSOperationQueue *queue;
}
 
+ (id)shared;
- (void)pageLoaded:(NSXMLDocument*)document;
 
@end
#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中,两件事正在发生。第一,在初始化方法中,NSOperationQueue装载一些URL数组。然后当应用程序完成装载的时候,也就是在被应用程序实例调用的applicationDidFinishLaunching方法中,通过url数组循环,为每个url创建一个任务,然后放置这些任务到NSOperationQueue中。只要任何一个NSOperation被安排到队列中,就回立刻被队列获取,然后分配它到一个NSThread中,然后NSThread就会运行NSOperation中的main函数中的方法。一旦操作完成,线程就报告给队列,然后队列就释放这个操作。

NSOperationQueue同步

在这个简单的例子中,很困难导入足够多的对象,使之并行运行。然而,如果你运行的任务需要花费大量的时间,你将会看到此队列同时运行很多任务。幸运的是,如果你想降低并发任务的数量,你能在AppDelegate的初始化方法中, 如下的设置:

- (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;
}

这样的话,在相同的时间,队列就会被控制在只有2个操作在运行。剩下的操作将会等待,直到头两个操作被执行完,然后队列才能得到机会去执行其他的操作,直到队列空为止。

总结

这些是NSOperation和NSOperationQueue最基础的操作。你会注意到代码中很多地方都与NSOperation,NSOperationQueue的建立和使用没有关系。事实上使用NSOperation和NSOperationQueue的代码是惊人的小。然而,使用如此少的代码,你就可以在程序中使用多线程,所以它提供了一个很好的方式方便的管理应用程序中复杂的任务。

如果你喜欢这篇文章,谢谢你的赞赏

图3

如有疑问请联系我