多線程開發是一件需要特別精心的事情,即使是對有多年開發經驗的工程師來說。
為了能讓初級開發工程師也能使用多線程,同時還要簡化復雜性。各種編程工具提供了各自的辦法。對於iOS來說,建議在盡可能的情況下避免直接操作線程,使用比如NSOperationQueue這樣的機制。
可以把NSOperationQueue看作一個線程池,可往線程池中添加操作(NSOperation)到隊列中。線程池中的線程可看作消費者,從隊列中取走操作,並執行它。
你可以設置線程池中只有一個線程,這樣,各個操作就可以認為是近似的順序執行了。為什么說是近似呢,后面會做解釋。
編寫最簡單的示例
先寫個最簡單的示例。
編寫一個NSOperation的子類,只需實現main方法。這里非常類似Java的Thread,你可以繼承它,並覆蓋run方法,在該方法里面寫入需要執行的代碼。這里的main方法和run方法作用是相似的。
頭文件:
@interface MyTask : NSOperation {
int operationId;
}@property int operationId;
@end
這里的operationId屬性不是必須的,是我想在后面標識區分多個Task的標識位。
m文件:
@implementation MyTask
@synthesize operationId;
- (void)main{
NSLog(@"task %i run … ",operationId);
[NSThread sleepForTimeInterval:10];
NSLog(@"task %i is finished. ",operationId);
}@end
這里模擬了一個耗時10秒鍾的操作。
下面需要把Task加入到隊列中:
- (void)viewDidLoad {
[super viewDidLoad];
queue=[[NSOperationQueue alloc] init];
int index=1;
MyTask *task=[[[MyTask alloc] init] autorelease];
task.operationId=index++;
[queue addOperation:task];
我直接找了個Controller的方法寫上了。運行結果是,界面出現了,而task還未執行完,說明是多線程的。10秒鍾后,日志打印完畢,類似這樣:
2011-07-18 15:59:14.622 MultiThreadTest[24271:6103] task 1 run …
2011-07-18 15:59:24.623 MultiThreadTest[24271:6103] task 1 is finished.
可以向操作隊列(NSOperationQueue)增加多個操作,比如這樣:
- (void)viewDidLoad {
[super viewDidLoad];
queue=[[NSOperationQueue alloc] init];
int index=1;
MyTask *task=[[[MyTask alloc] init] autorelease];
task.operationId=index++;
[queue addOperation:task];
task=[[[MyTask alloc] init] autorelease];
task.operationId=index++;[queue addOperation:task];
}
那么打印出的內容是不定的,有可能是這樣:
2011-07-18 15:49:48.087 MultiThreadTest[24139:6203] task 1 run …
2011-07-18 15:49:48.087 MultiThreadTest[24139:1903] task 2 run …
2011-07-18 15:49:58.122 MultiThreadTest[24139:6203] task 1 is finished.
2011-07-18 15:49:58.122 MultiThreadTest[24139:1903] task 2 is finished.
甚至有可能是這樣:
2011-07-18 15:52:24.686 MultiThreadTest[24168:1b03] task 2 run …
2011-07-18 15:52:24.685 MultiThreadTest[24168:6003] task 1 run …
2011-07-18 15:52:34.708 MultiThreadTest[24168:1b03] task 2 is finished.
2011-07-18 15:52:34.708 MultiThreadTest[24168:6003] task 1 is finished.
因為兩個操作提交的時間間隔很近,線程池中的線程,誰先啟動是不定的。
那么,如果需要嚴格意義的順序執行,怎么辦呢?
處理操作之間的依賴關系
如果操作直接有依賴關系,比如第二個操作必須等第一個操作結束后再執行,需要這樣寫:
queue=[[NSOperationQueue alloc] init];
int index=1;
MyTask *task=[[[MyTask alloc] init] autorelease];
task.operationId=index++;[queue addOperation:task];
task=[[[MyTask alloc] init] autorelease];
task.operationId=index++;if ([[queue operations] count]>0) {
MyTask *theBeforeTask=[[queue operations] lastObject];
[task addDependency:theBeforeTask];
}[queue addOperation:task];
這樣,即使是多線程情況下,可以看到操作是嚴格按照先后次序執行的。
控制線程池中的線程數
可以通過類似下面的代碼:
[queue setMaxConcurrentOperationCount:2];
來設置線程池中的線程數,也就是並發操作數。默認情況下是-1,也就是沒有限制,同時運行隊列中的全部操作。
另附代碼實例:
只有代碼,解釋以后加上:
#import <UIKit/UIKit.h>
@interface tOQViewController : UIViewController {
NSOperationQueue *myOQ;
}
-(void)doSomething;
@end
實現文件如下:
#import "tOQViewController.h"
@implementation tOQViewController
-(void)doSomething
{
NSLog(@"in thread");
}
- (void)dealloc
{
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
[super viewDidLoad];
myOQ = [[NSOperationQueue alloc] init];
[myOQ setMaxConcurrentOperationCount:1];
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomething) object:nil];
[myOQ addOperation:op];
[op release];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
@end