第三篇:多線程編程的多種方式
iOS執(zhí)行多線程編程常用的有以下幾種方式
- NSThread
- GCD
- NSOperation
本文簡(jiǎn)單介紹NSThread和詳細(xì)介紹NSOperation
3.1 NSThread
這套方案是經(jīng)過(guò)蘋(píng)果封裝后的,并且完全面向?qū)ο蟮摹K阅憧梢灾苯硬倏鼐€程對(duì)象,非常直觀和方便。但是,它的生命周期還是需要我們手動(dòng)管理,所以這套方案也是偶爾用用,比如 [NSThread currentThread],它可以獲取當(dāng)前線程類(lèi),你就可以知道當(dāng)前線程的各種屬性,用于調(diào)試十分方便。下面來(lái)看看它的一些用法。
** 創(chuàng)建并啟動(dòng) **
先創(chuàng)建線程類(lèi),再啟動(dòng)
// 創(chuàng)建
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
// 啟動(dòng)
[thread start];
創(chuàng)建并自動(dòng)啟動(dòng)
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
使用 NSObject 的方法創(chuàng)建并自動(dòng)啟動(dòng)
[self performSelectorInBackground:@selector(run:) withObject:nil];
** 其他方法 **
除了創(chuàng)建啟動(dòng)外,NSThread 還以很多方法,下面我列舉一些常見(jiàn)的方法,當(dāng)然我列舉的并不完整,更多方法大家可以去類(lèi)的定義里去看。
//取消線程
- (void)cancel;
//啟動(dòng)線程
- (void)start;
//判斷某個(gè)線程的狀態(tài)的屬性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;
//設(shè)置和獲取線程名字
-(void)setName:(NSString *)n;
-(NSString *)name;
//獲取當(dāng)前線程信息
+ (NSThread *)currentThread;
//獲取主線程信息
+ (NSThread *)mainThread;
//使當(dāng)前線程暫停一段時(shí)間,或者暫停到某個(gè)時(shí)刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;
** GCD見(jiàn)上篇 **
3.2 NSOperation和NSOperationQueue
NSOperation 是蘋(píng)果公司對(duì) GCD 的封裝,完全面向?qū)ο螅允褂闷饋?lái)更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分別對(duì)應(yīng) GCD 的 任務(wù) 和 隊(duì)列 。操作步驟也很好理解:
- 將要執(zhí)行的任務(wù)封裝到一個(gè) NSOperation 對(duì)象中。
- 將此任務(wù)添加到一個(gè) NSOperationQueue 對(duì)象中。
然后系統(tǒng)就會(huì)自動(dòng)在執(zhí)行任務(wù)。至于同步還是異步、串行還是并行請(qǐng)繼續(xù)往下看:
** 添加任務(wù) **
值得說(shuō)明的是,** NSOperation 只是一個(gè)抽象類(lèi),所以不能封裝任務(wù) **。但它有 2 個(gè)子類(lèi)用于封裝任務(wù)。分別是:NSInvocationOperation 和 NSBlockOperation 。創(chuàng)建一個(gè) Operation 后,需要調(diào)用 start 方法來(lái)啟動(dòng)任務(wù),它會(huì) 默認(rèn)在當(dāng)前隊(duì)列同步執(zhí)行。當(dāng)然你也可以在中途取消一個(gè)任務(wù),只需要調(diào)用其 cancel 方法即可。
- NSInvocationOperation : 需要傳入一個(gè)方法名。
//1.創(chuàng)建NSInvocationOperation對(duì)象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
//2.開(kāi)始執(zhí)行
[operation start];
- NSBlockOperation
//1.創(chuàng)建NSBlockOperation對(duì)象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//2.開(kāi)始任務(wù)
[operation start];
之前說(shuō)過(guò)這樣的任務(wù),默認(rèn)會(huì)在當(dāng)前線程執(zhí)行。但是 NSBlockOperation 還有一個(gè)方法:addExecutionBlock: ,通過(guò)這個(gè)方法可以給 Operation 添加多個(gè)執(zhí)行 Block。這樣 Operation 中的任務(wù) 會(huì)并發(fā)執(zhí)行,它會(huì) 在主線程和其它的多個(gè)線程 執(zhí)行這些任務(wù),注意下面的打印結(jié)果:
//1.創(chuàng)建NSBlockOperation對(duì)象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//添加多個(gè)Block
for (NSInteger i = 0; i < 5; i++) {
[operation addExecutionBlock:^{
NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
}];
}
//2.開(kāi)始任務(wù)
[operation start];
打印結(jié)果
2015-07-28 17:50:16.585 test[17527:4095467] 第2次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}
2015-07-28 17:50:16.585 test[17527:4095666] 第1次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}
2015-07-28 17:50:16.585 test[17527:4095665] <NSThread: 0x7ff5c961b610>{number = 3, name = (null)}
2015-07-28 17:50:16.585 test[17527:4095662] 第0次 - <NSThread: 0x7ff5c948d310>{number = 2, name = (null)}
2015-07-28 17:50:16.586 test[17527:4095666] 第3次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}
2015-07-28 17:50:16.586 test[17527:4095467] 第4次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}
** NOTE:** addExecutionBlock 方法必須在 start() 方法之前執(zhí)行,否則就會(huì)報(bào)錯(cuò):
‘*** -[NSBlockOperation addExecutionBlock:]: blocks cannot be added after the operation has started executing or finished'
NOTE:大家可能發(fā)現(xiàn)了一個(gè)問(wèn)題,為什么我在 Swift 里打印輸出使用 NSLog() 而不是 println() 呢?原因是使用 print() / println() 輸出的話,它會(huì)簡(jiǎn)單地使用 流(stream) 的概念,學(xué)過(guò) C++ 的都知道。它會(huì)把需要輸出的每個(gè)字符一個(gè)一個(gè)的輸出到控制臺(tái)。普通使用并沒(méi)有問(wèn)題,可是當(dāng)多線程同步輸出的時(shí)候問(wèn)題就來(lái)了,由于很多 println() 同時(shí)打印,就會(huì)導(dǎo)致控制臺(tái)上的字符混亂的堆在一起,而NSLog() 就沒(méi)有這個(gè)問(wèn)題。到底是什么樣子的呢?你可以把上面 NSLog() 改為 println() ,然后一試便知。 更多 NSLog() 與 println() 的區(qū)別看這里
- 自定義Operation
1.創(chuàng)建自定義Operation類(lèi),繼承自NSOperation
2.重寫(xiě)自定義Operation的main方法
重寫(xiě)- (void)main方法,在里面實(shí)現(xiàn)想執(zhí)行的任務(wù)
創(chuàng)建自動(dòng)釋放池(因?yàn)槿绻钱惒讲僮?,無(wú)法訪問(wèn)主線程的自動(dòng)釋放池),新版本已經(jīng)不再需要手動(dòng)創(chuàng)建自動(dòng)釋放池
3.通過(guò) "- (BOOL)isCancelled" 方法檢測(cè)操作是否被取消,對(duì)取消做出響應(yīng)
之前提到過(guò)NSOperation只能對(duì)還未執(zhí)行的任務(wù)做取消操作,是由于系統(tǒng)封裝的方法內(nèi)部我們無(wú)法訪問(wèn),而自定義Operation中,我們需要重寫(xiě)main方法,在這里檢測(cè)到isCancelled屬性為YES時(shí),直接返回,不執(zhí)行后面的操作即可取消正在執(zhí)行的操作,雖然能夠?qū)崿F(xiàn)取消正在執(zhí)行的操作,但并不是立刻停止的,而是在調(diào)用了 operation 的 cancel 方法之后的下一個(gè) isCancelled 的檢查點(diǎn)取消的
4.在controller中調(diào)用start方法,或者添加到隊(duì)列, main方法會(huì)被調(diào)用
通過(guò)自定義Operation, 模擬異步下載圖片
1.自定義DownloadOperation類(lèi),繼承自NSOperation
2.聲明屬性
// url地址
@property (nonatomic,copy) NSString *urlString;
// 完成回調(diào)
@property (nonatomic,copy) void(^completeHandler)(UIImage *img);
3.在DownloadOperation類(lèi)中重寫(xiě)main方法:
#import "DownloadOperation.h"
@implementation DownloadOperation
- (void)main{
//@autoreleasepool {
// 更新后不再需要手動(dòng)創(chuàng)建,系統(tǒng)內(nèi)部做過(guò)優(yōu)化
//}
NSAssert(self.completeHandler != nil, @"completeHandler == nil");
// 下載圖片
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:self.urlString]];
UIImage *image = [UIImage imageWithData:data];
// 取消操作
if (self.isCancelled) {
NSLog(@"取消操作");
return;
}
// 返回主線程刷新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.completeHandler(image);
}];
}
@end
4.使用:
#import "ViewController.h"
#import "DownloadOperation.h"
@interface ViewController ()
@end
@implementation ViewController{
NSOperationQueue *_queue;
UIImageView *_imageView;
}
- (void)viewDidLoad {
[super viewDidLoad];
_queue = [[NSOperationQueue alloc] init];
_imageView = [[UIImageView alloc] initWithFrame:[UIScreen mainScreen].bounds];
_imageView.contentMode = UIViewContentModeCenter;
[self.view addSubview:_imageView];
// 1.創(chuàng)建自定義的Operation對(duì)象 (下載圖片操作)
DownloadOperation *operation = [[DownloadOperation alloc] init];
operation.urlString = @"http://t1.mmonly.cc/mmonly/2014/201407/129/7.jpg";
[operation setCompleteHandler:^(UIImage *img) {
// 更新UI
_imageView.image = img;
}];
// 2.把操作添加到隊(duì)列中
[_queue addOperation:operation];
}
@end
示例代碼中先通過(guò)alloc]init實(shí)例化了操作對(duì)象,再通過(guò)屬性完成參數(shù)傳遞和下載圖片的回調(diào)
在使用系統(tǒng)為我們提供的NSOperation子類(lèi)中,系統(tǒng)提供了類(lèi)方法直接使用,所以為了方便使用,同樣可以自定義一個(gè)類(lèi)方法,完成參數(shù)傳遞和回調(diào), 結(jié)合操作的取消一同進(jìn)行演示
代碼優(yōu)化(自定義類(lèi)方法):
.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface DownloadOperation : NSOperation
// url地址
@property (nonatomic,copy) NSString *urlString;
// 完成回調(diào)
@property (nonatomic,copy) void(^completeHandler)(UIImage *img);
// 下載圖片類(lèi)方法
+(instancetype)downloadImageUrlString:(NSString *)urlString completeHandler:(void(^)(UIImage *img))completeHandler;
@end
.m
#import "DownloadOperation.h"
@implementation DownloadOperation
- (void)main{
NSAssert(self.completeHandler != nil, @"completeHandler == nil");
// 下載圖片
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:self.urlString]];
UIImage *image = [UIImage imageWithData:data];
// 取消操作
if (self.isCancelled) {
NSLog(@"取消操作");
return;
}
// 返回主線程刷新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.completeHandler(image);
}];
}
// 自定義類(lèi)方法下載圖片
+ (instancetype)downloadImageUrlString:(NSString *)urlString completeHandler:(void (^)(UIImage *))completeHandler{
DownloadOperation *operation = [[DownloadOperation alloc] init];
operation.urlString = urlString;
operation.completeHandler = completeHandler;
return operation;
}
@end
外部調(diào)用
#import "ViewController.h"
#import "DownloadOperation.h"
@interface ViewController ()
@end
@implementation ViewController{
NSOperationQueue *_queue;
}
- (void)viewDidLoad {
[super viewDidLoad];
_queue = [[NSOperationQueue alloc] init];
_queue.maxConcurrentOperationCount = 3;
for (int i = 0; i < 20; i ++) {
// 1.創(chuàng)建自定義的Operation對(duì)象
DownloadOperation *operation = [DownloadOperation downloadImageUrlString:@"http://t1.mmonly.cc/uploads/tu/201607/tt/1alqhs1gwxo.jpg" completeHandler:^(UIImage *img) {
NSLog(@"%d --> (%@)",i,[NSThread currentThread]);
}];
// 2.把操作添加到隊(duì)列中
[_queue addOperation:operation];
}
}
// 觸摸屏幕時(shí)取消所有操作(包括正在執(zhí)行的操作)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[_queue cancelAllOperations];
}
@end