iPhone多线程开发之线程队列NSOperationQueue与自定义Protocol

一.多线程对于iPhone应用程序开发很重要

      在一个程序中,这些独立运行的程序片断叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”。多线程处理一个常见的例子就是用户界面设计,利用线程,用户可按下一个按钮,然后程序会立即作出响应,而不是让用户等待程序完成了当前任务以后才开始响应,这就是所谓的阻塞主线程使得界面被冻结。 特别是在一些基于网络的应用里,多线程技术显得尤为重要。当用户向服务器发出一个请求时,你需要做的有发送请求,接收数据,有时还需要解析数据(xml),但是如果你不是启动一个线程去做这些事情的话,那么做这些动作的时候你的界面将会被“冻结”,给用户造成不友好的体验。所以我们需要多线程技术。

二.NSOperationQueue

      在这之前,你可能需要了解什么是NSOperation,这在官方文档里面写的很清楚。线程是从NSOperation中的main函数开始执行。类似于java中的多线程机制中的run函数。

下面让我们来看看NSOperationQueue的方法:

- (void)addOperation:(NSOperation *)operation

the specified operation remains in the queue until it finishes executing

创建一个NSOperationQueue对象,然后创建NSOperation添加到NSOperationQueue中。NSOperationQueue会为每一个NSOperation创建一个线程,并按照它们被添加的顺序执行(或者指定的顺序)。NSOperationQueue会自己处理autorelease pools 和其他的garbage,而不需要像在自己手动创建多线程时去费心管理这些事情,当把NSOperation的对象放进队列中,此线程就会按照先进先出的方式等待运行,并且是从main函数开始运行。

三.自定义协议与委托机制

      协议就是约定好的一组方法,协议可以实现对象之间的通信,好像是一种通知或者回调机制:“在某件事情发生的时候通知我”,是cocoa设计模式的核心。经常与多线程编程搭配使用,用来通知主线程可以继续执行。

在下面的例子中我们看看这是怎么实现的:

首先,LazyTableAppDelegate.m里面

实现了NSURLConnection的代理,然后异步的等待数据下载结束之后,则从客户端启动另一个线程开始解析数据

1.ParseOperation *parser = [[ParseOperation alloc] initWithData:appListData delegate:self];

2.[queue addOperation:parser];

ParseOperation继承了NSOperation,然后将这个线程放入线程线程队列,应该是一个优先级队列,NSOperationQueue,如果此线程位于队列的头,则这个线程开始执行。线程的任务完成后,要通知主线程完成了相关的工作,那么什么时间返回主进程呢?这就用到了代理。[self.delegate didFinishParsing:self.workingArray] self.delegate代理指向的是是LazyTableAppDelegate的对象,这样就完成了对LazyTableAppDelegate的回调,需要注意的是,在LazyTableAppDelegate里实现了在ParseOperation.h里声明的那个协议,协议的声明方法见下面代码:

28  @protocol ParseOperationDelegate
29  - (void)didFinishParsing:(NSArray *)appList;
30  - (void)parseErrorOccurred:(NSError *)error;
31 @end

这里只是声明这些方法,在哪里定义呢,在需要被回调的地方定义,也就是LazyTableAppDelegate。

看一下回调的方法: [self.delegate didFinishParsing:self.workingArray];

这个方法是在线程ParseOperation执行结束的时候调用。self.delegate目前是指向的是LazyTableAppDelegate类的对象,这个对象则调用所继承的协议里面声明的方法- (void)didFinishParsing:(NSArray *)appList,这样就完成了回调。如何回到主线程的呢?看看(void)didFinishParsing:(NSArray *)appList这个函数是如何在LazyTableAppDelegate.m里定义的:

37  - (void)didFinishParsing:(NSArray *)appList
38 {
39 [self performSelectorOnMainThread:@selector(handleLoadedApps:) withObject:appList waitUntilDone:NO];
40 self.queue = nil;
41 }

一切真相大白,这个函数里面又调用了一个方法handleLoadedApps,并且使用performSelectorOnMainThread,使之在主线程中执行操作,这时候就可以释放我们的线程池了。一切又回归到主线程中了。

四.示例代码(官方程序:lazytable)

LazyTableAppDelegate.h 

1 #import <UIKit/UIKit.h>
2 #import "AppRecord.h"
3 #import "ParseOperation.h"
4
5 @class RootViewController;
6
7 @interface LazyTableAppDelegate : NSObject <UIApplicationDelegate, ParseOperationDelegate>
8 {
9 UIWindow *window;
10 UINavigationController *navigationController;
11
12 // this view controller hosts our table of top paid apps
13   RootViewController *rootViewController;
14
15 // the list of apps shared with "RootViewController"
16   NSMutableArray *appRecords;
17
18 // the queue to run our "ParseOperation"
19   NSOperationQueue *queue;
20
21 // RSS feed network connection to the App Store
22   NSURLConnection *appListFeedConnection;
23 NSMutableData *appListData;
24 }
25
26 @property (nonatomic, retain) IBOutlet UIWindow *window;
27 @property (nonatomic, retain) IBOutlet UINavigationController *navigationController;
28 @property (nonatomic, retain) IBOutlet RootViewController *rootViewController;
29 @property (nonatomic, retain) NSMutableArray *appRecords;
30 @property (nonatomic, retain) NSOperationQueue *queue;
31 @property (nonatomic, retain) NSURLConnection *appListFeedConnection;
32 @property (nonatomic, retain) NSMutableData *appListData;
33
34 @end
35  

LazyTableAppDelegate.m

1 #import "LazyTableAppDelegate.h"
2 #import "RootViewController.h"
3 #import "ParseOperation.h"
4
5 #import <CFNetwork/CFNetwork.h>
6
7  static NSString *const TopPaidAppsFeed =
8 @"http://phobos.apple.com/WebObjects/MZStoreServices.woa/ws/RSS/toppaidapplications/limit=75/xml";
9
10
11 @implementation LazyTableAppDelegate
12
13 @synthesize window, navigationController, appRecords, rootViewController, queue, appListFeedConnection, appListData;
14
15
16  #pragma mark -
17
18  - (void)applicationDidFinishLaunching:(UIApplication *)application
19 {
20 // Configure and show the window
21   [window addSubview:[self.navigationController view]];
22 [window makeKeyAndVisible];
23 // self.appRecords,rootViewController.entries指向同一快内存
24   self.appRecords = [NSMutableArray array];
25 rootViewController.entries = self.appRecords;
26
27 NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:TopPaidAppsFeed]];
28 self.appListFeedConnection = [[[NSURLConnection alloc] initWithRequest:urlRequest delegate:self] autorelease];
29 }
30
31  - (void)handleLoadedApps:(NSArray *)loadedApps
32 {
33 [self.appRecords addObjectsFromArray:loadedApps];
34 [rootViewController.tableView reloadData];
35 }
36
37  - (void)didFinishParsing:(NSArray *)appList
38 {
39 [self performSelectorOnMainThread:@selector(handleLoadedApps:) withObject:appList waitUntilDone:NO];
40 self.queue = nil;
41 }
42
43  - (void)parseErrorOccurred:(NSError *)error
44 {
45 [self performSelectorOnMainThread:@selector(handleError:) withObject:error waitUntilDone:NO];
46 }
47
48  #pragma mark -
49 #pragma mark NSURLConnection delegate methods
50
51 - (void)handleError:(NSError *)error
52 {
53 ...
54 }
55
56 // The following are delegate methods for NSURLConnection. Similar to callback functions, this is how
57 // the connection object, which is working in the background, can asynchronously communicate back to
58 // its delegate on the thread from which it was started - in this case, the main thread.
59 //
60
61 // -------------------------------------------------------------------------------
62 // connection:didReceiveResponse:response
63 // -------------------------------------------------------------------------------
64 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
65 {
66 self.appListData = [NSMutableData data]; // start off with new data
67 }
68
69 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
70 {
71 [appListData appendData:data]; // append incoming data
72 }
73
74 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
75 {
76 ...
77 }
78
79 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
80 {
81 self.appListFeedConnection = nil; // release our connection
82
83 [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
84
85 // create the queue to run our ParseOperation
86 self.queue = [[NSOperationQueue alloc] init];
87
88 // create an ParseOperation (NSOperation subclass) to the RSS feed data so that the UI is not blocked
89 // "ownership of appListData has been transferred to the parse operation and should no longer be
90 // referenced in this thread.
91 //
92 ParseOperation *parser = [[ParseOperation alloc] initWithData:appListData delegate:self];
93
94 [queue addOperation:parser]; // this will start the "ParseOperation"
95
96 [parser release];
97
98 // ownership of appListData has been transferred to the parse operation
99 // and should no longer be referenced in this thread
100 self.appListData = nil;
101 }
102
103 - (void)dealloc
104 {
105 ...
106 [window release];
107 [super dealloc];
108 }
109
110 @end

ParseOperation.h

1 @class AppRecord;
2 @protocol ParseOperationDelegate;
3
4 @interface ParseOperation : NSOperation
5 {
6 @private
7 id <ParseOperationDelegate> delegate;
8
9 NSData *dataToParse;
10 NSMutableArray *workingArray;
11 AppRecord *workingEntry;
12 NSMutableString *workingPropertyString;
13 NSArray *elementsToParse;
14 BOOL storingCharacterData;
15 }
16 @property (nonatomic, assign) id <ParseOperationDelegate> delegate;
17 @property (nonatomic, retain) NSData *dataToParse;
18 @property (nonatomic, retain) NSMutableArray *workingArray;
19 @property (nonatomic, retain) AppRecord *workingEntry;
20 @property (nonatomic, retain) NSMutableString *workingPropertyString;
21 @property (nonatomic, retain) NSArray *elementsToParse;
22 @property (nonatomic, assign) BOOL storingCharacterData;
23  - (id)initWithData:(NSData *)data delegate:(id <ParseOperationDelegate>)theDelegate;
24
25 @end
26
27  //声明协议
28  @protocol ParseOperationDelegate
29  - (void)didFinishParsing:(NSArray *)appList;
30  - (void)parseErrorOccurred:(NSError *)error;
31 @end
32  

ParseOperation.m

1 #import "ParseOperation.h"
2 #import "AppRecord.h"
3 #import "LazyTableAppDelegate.h"
4
5 @implementation ParseOperation
6
7 @synthesize delegate, dataToParse, workingArray, workingEntry, workingPropertyString, elementsToParse, storingCharacterData;
8
9  - (id)initWithData:(NSData *)data delegate:(id <ParseOperationDelegate>)theDelegate
10 {
11 self = [super init];
12 if (self != nil)
13 {
14 self.dataToParse = data;
15 self.delegate = theDelegate;
16 self.elementsToParse = [NSArray arrayWithObjects:kIDStr, kNameStr, kImageStr, kArtistStr, nil];
17 }
18 return self;
19 }
20
21  - (void)dealloc
22 {
23 [dataToParse release];
24 [workingEntry release];
25 [workingPropertyString release];
26 [workingArray release];
27
28 [super dealloc];
29 }
30
31
32  - (void)main
33 {
34 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
35
36 self.workingArray = [NSMutableArray array];
37 self.workingPropertyString = [NSMutableString string];
38
39 NSXMLParser *parser = [[NSXMLParser alloc] initWithData:dataToParse];
40 [parser setDelegate:self];
41 [parser parse];
42
43 if (![self isCancelled])
44 {
45 [self.delegate didFinishParsing:self.workingArray];
46 }
47
48 self.workingArray = nil;
49 self.workingPropertyString = nil;
50 self.dataToParse = nil;
51
52 [parser release];
53 [pool release];
54 }
55
56
57  #pragma mark -
58 #pragma mark RSS processing
59
60 - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName
61 namespaceURI:(NSString *)namespaceURI
62 qualifiedName:(NSString *)qName
63 attributes:(NSDictionary *)attributeDict
64 {
65 ...
66 }
67 - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName
68 namespaceURI:(NSString *)namespaceURI
69 qualifiedName:(NSString *)qName
70 {
71 ...
72 }
73 - (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
74 {
75 ...
76 }
77 - (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
78 {
79 [delegate parseErrorOccurred:parseError];
80 }
81 @end
82
posted @ 2010-12-03 15:36  樱色布  阅读(3530)  评论(0编辑  收藏  举报