NSOperation 是iOS中比較高級的并發(fā)編程的操作!
NSOperation : 封裝了 GCD 中的"任務"!
NSOperationQueue : 封裝了 GCD 中的"隊列"!
-
NSOperation是一個"抽象類",不能直接使用 - 抽象類的用處是定義子類共有的屬性和方法
- 在蘋果的頭文件中,有些抽象類和子類的定義是在同一個頭文件中的
- 子類:
-
NSInvocationOperation(使用和按鈕/ target) -
NSBlockOperation(利用block 封裝任務)
-
-
NSOperationQueue隊列
其他基本的抽象類
UIGestureRecognizerCAAnimationCAPropertyAnimation
一. 基本演練
NSInvocationOperation
start
-
start方法 會在當前線程執(zhí)行@selector方法
- (void)opDemo1 {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"Invocation"];
// start方法:直接開始,會在當前線程執(zhí)行 @selector 方法
[op start];
}
- (void)downloadImage:(id)obj {
NSLog(@"%@ %@", [NSThread currentThread], obj);
}
添加到隊列
- 將操作添加到隊列,會"異步"執(zhí)行
selector方法
- (void)opDemo2 {
NSOperationQueue *q = [[NSOperationQueue alloc] init];
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"queue"];
[q addOperation:op];
}
添加多個操作
- (void)opDemo3 {
NSOperationQueue *q = [[NSOperationQueue alloc] init];
for (int i = 0; i < 10; ++i) {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@(i)];
[q addOperation:op];
}
}
執(zhí)行效果:會開啟多條線程,而且不是順序執(zhí)行。與GCD中并發(fā)隊列&異步執(zhí)行效果一樣!
結論,在 NSOperation 中:
- 操作 -> 異步執(zhí)行的任務
- 隊列 -> 全局隊列
NSBlockOperation
- (void)opDemo4 {
NSOperationQueue *q = [[NSOperationQueue alloc] init];
#warning 打印結果是在主線程中,說明直接在主線程執(zhí)行,沒有開辟新的線程
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
[q addOperation:op];
}
使用 block 來定義操作,所有的代碼寫在一起,更簡單,便于維護!
更簡單的,直接添加 Block
- (void)opDemo5 {
NSOperationQueue *q = [[NSOperationQueue alloc] init];
for (int i = 0; i < 10; ++i) {
[q addOperationWithBlock:^{
NSLog(@"%@ %d", [NSThread currentThread], i);
}];
}
}
向隊列中添加不同的操作
- (void)opDemo5 {
NSOperationQueue *q = [[NSOperationQueue alloc] init];
for (int i = 0; i < 10; ++i) {
[q addOperationWithBlock:^{
NSLog(@"%@ %d", [NSThread currentThread], i);
}];
}
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block %@", [NSThread currentThread]);
}];
[q addOperation:op1];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"invocation"];
[q addOperation:op2];
}
- 可以向
NSOperationQueue中添加任意NSOperation的子類
線程間通訊
- (void)opDemo6 {
NSOperationQueue *q = [[NSOperationQueue alloc] init];
[q addOperationWithBlock:^{
NSLog(@"耗時操作 %@", [NSThread currentThread]);
// 主線程更新 UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"更新 UI %@", [NSThread currentThread]);
}];
}];
}
二. 高級演練
全局隊列
/// 全局操作隊列,統(tǒng)一管理所有的異步操作
@property (nonatomic, strong) NSOperationQueue *queue;
- (NSOperationQueue *)queue {
if (_queue == nil) {
_queue = [[NSOperationQueue alloc] init];
}
return _queue;
}
最大并發(fā)操作數(shù)
/// MARK: - 最大并發(fā)操作數(shù)
- (void)opDemo1 {
// 設置同時并發(fā)操作數(shù)
self.queue.maxConcurrentOperationCount = 2;
NSLog(@"start");
for (int i = 0; i < 10; ++i) {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"%@ %d", [NSThread currentThread], i);
}];
[self.queue addOperation:op];
}
}
暫停 & 繼續(xù)
/// MARK: - 暫停 & 繼續(xù)
- (IBAction)pauseAndResume {
if (self.queue.operationCount == 0) {
NSLog(@"沒有操作");
return;
}
// 暫?;蛘呃^續(xù)
self.queue.suspended = !self.queue.isSuspended;
if (self.queue.isSuspended) {
NSLog(@"暫停 %tu", self.queue.operationCount);
} else {
NSLog(@"繼續(xù) %tu", self.queue.operationCount);
}
}
- 隊列掛起,當前"沒有完成的操作",是包含在隊列的操作數(shù)中的
- 隊列掛起,不會影響已經(jīng)執(zhí)行操作的執(zhí)行狀態(tài)
- 對列一旦被掛起,再添加的操作不會被調(diào)度
取消全部操作
/// MARK: - 取消所有操作
- (IBAction)cancelAll {
if (self.queue.operationCount == 0) {
NSLog(@"沒有操作");
return;
}
// 取消對列中的所有操作,同樣不會影響到正在執(zhí)行中的操作!
[self.queue cancelAllOperations];
NSLog(@"取消全部操作 %tu", self.queue.operationCount);
}
- 取消隊列中所有的操作
- 不會取消正在執(zhí)行中的操作
- 不會影響隊列的掛起狀態(tài)
依賴關系
#warning 不要添加循環(huán)操作依賴,一定要在添加進操作隊列之前設置操作依賴
- (void)dependency {
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"登錄 %@", [NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"付費 %@", [NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下載 %@", [NSThread currentThread]);
}];
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"通知用戶 %@", [NSThread currentThread]);
}];
[op2 addDependency:op1];
[op3 addDependency:op2];
[op4 addDependency:op3];
// 注意不要循環(huán)依賴
// [op1 addDependency:op4];
[self.queue addOperations:@[op1, op2, op3] waitUntilFinished:NO];
[[NSOperationQueue mainQueue] addOperation:op4];
NSLog(@"come here");
}
追加操作
當 NSBlockOperation 中的任務書 > 1 之后,無論是將操作添加到主線程,還是在主線程直接執(zhí)行start, NSBlockOperation中的任務執(zhí)行順序都不確定,執(zhí)行線程也不確定!
一般在開發(fā)中,要避免向NSBlockOperation 中追加任務
如果任務都是在主線程中執(zhí)行,并且不需要保證執(zhí)行順序,可以直接追加任務
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 1. 實例化操作對象
NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"11111--->%@", [NSThread currentThread]);
}];
// 2. 往當前操作中追加操作一
[blockOperation1 addExecutionBlock:^{
NSLog(@"追加任務1--->%@", [NSThread currentThread]);
}];
// 2. 往當前操作中追加操作二
[blockOperation1 addExecutionBlock:^{
NSLog(@"追加任務2--->%@", [NSThread currentThread]);
}];
NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"22222--->%@", [NSThread currentThread]);
}];
NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"33333--->%@", [NSThread currentThread]);
}];
// 將操作添加到主隊列中
// [[NSOperationQueue mainQueue] addOperation:blockOperation1];
// [[NSOperationQueue mainQueue] addOperation:blockOperation2];
// [[NSOperationQueue mainQueue] addOperation:blockOperation3];
/*
輸出結果:
11111---><NSThread: 0x7fed62e065f0>{number = 1, name = main}
追加任務2---><NSThread: 0x7fed62e065f0>{number = 1, name = main}
追加任務1---><NSThread: 0x7fed62f07ba0>{number = 4, name = (null)}
22222---><NSThread: 0x7fed62e065f0>{number = 1, name = main}
33333---><NSThread: 0x7fed62e065f0>{number = 1, name = main}
*/
// 將操作添加到非主隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:blockOperation1];
[queue addOperation:blockOperation2];
[queue addOperation:blockOperation3];
/*
輸出結果:
22222---><NSThread: 0x7fba18f08a20>{number = 9, name = (null)}
11111---><NSThread: 0x7fba18f03780>{number = 7, name = (null)}
33333---><NSThread: 0x7fba18d99c20>{number = 10, name = (null)}
追加任務1---><NSThread: 0x7fba18f08770>{number = 8, name = (null)}
追加任務2---><NSThread: 0x7fba18c06220>{number = 11, name = (null)}
*/
}
三. 與 GCD 的對比
-
GCD
- 將
任務(block)添加到隊列(串行/并發(fā)/主隊列),并且指定任務執(zhí)行的函數(shù)(同步/異步) - GCD是底層的C語言構成的API
- iOS 4.0 推出的,針對多核處理器的并發(fā)技術
- 在隊列中執(zhí)行的是由
block構成的任務,這是一個輕量級的數(shù)據(jù)結構 - 要停止已經(jīng)加入
queue的block需要寫復雜的代碼 - 需要通過
Barrier或者同步任務設置任務之間的依賴關系 - 只能設置隊列的優(yōu)先級
- 高級功能:
- 一次性 once
- 延遲操作 after
- 調(diào)度組
- 將
-
NSOperation
- 核心概念:把
操作(異步)添加到隊列(全局的并發(fā)隊列) - OC 框架,更加面向?qū)ο?,是?GCD 的封裝
- iOS 2.0 推出的,蘋果推出 GCD 之后,對 NSOperation 的底層全部重寫
-
Operation作為一個對象,為我們提供了更多的選擇 - 可以隨時取消已經(jīng)設定要準備執(zhí)行的任務,已經(jīng)執(zhí)行的除外
- 可以跨隊列設置操作的依賴關系
- 可以設置隊列中每一個操作的優(yōu)先級
- 高級功能:
- 最大操作并發(fā)數(shù)(GCD不好做)
- 繼續(xù)/暫停/全部取消
- 跨隊列設置操作的依賴關系
- 核心概念:把
四. 自定義操作
準備工作
- 自定義
DownloadImageOperation繼承自 NSOperation - 代碼調(diào)用
// 實例化自定義操作
DownloadImageOperation *op = [[DownloadImageOperation alloc] init];
// 將自定義操作添加到下載隊列
[self.downloadQueue addOperation:op];
需求驅(qū)動開發(fā)
目標一:設置自定義操作的執(zhí)行入口
對于自定義操作,只要重寫了
main方法,當隊列調(diào)度操作執(zhí)行時,會自動運行main方法
注意:為了能夠及時釋放內(nèi)存,main方法內(nèi)部一般建立一個自動釋放池,但是蘋果官方文檔不要求寫!
- (void)main {
@autoreleasepool {
NSLog(@"main--->%@", [NSThread currentThread]);
NSString *filePath = @"http://ww1.sinaimg.cn/bmiddle/c260f7abjw1exxbbyckd6j20gn0m8wgh.jpg";
UIImage *image = [self downLoadImageWithStr:filePath];
// 回到主線程設置UI
[[NSOperationQueue mainQueue] addOperation:[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"setupUI--->%@", [NSThread currentThread]);
self.imgView.image = image;
}]];
}
}
// 抽取出下載圖片的方法
- (UIImage *)downLoadImageWithStr:(NSString *)filePath {
// 1. 轉(zhuǎn)化為地址
NSURL *url = [NSURL URLWithString:filePath];
// 2. 轉(zhuǎn)化為NSData 二進制類型數(shù)據(jù)(下載方法,耗時方法)
NSData *data = [NSData dataWithContentsOfURL:url];
// 3. 轉(zhuǎn)化為圖片類型
UIImage *image = [UIImage imageWithData:data];
NSLog(@"downLoadImage ---> %@", [NSThread currentThread]);
return image;
}
目標二:給自定義參數(shù)傳遞參數(shù)
- 定義屬性
/** 承載圖片的容器 */
@property (nonatomic, strong) UIImageView *imgView;
- 代碼調(diào)用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"touchBegin--->%@", [NSThread currentThread]);
// 1. 實例化自定義操作對象
NSDownLoadOperation *operation = [[NSDownLoadOperation alloc] init];
// 2. 告訴操作在哪里顯示圖片
// 注意: 不能做以下改變 self.imageView = [[UIImageView alloc] init];
operation.imgView = self.imgView;
// 3.1 將自定義操作添加到下載隊列,操作啟動后會執(zhí)行 main
[self.queue addOperation:operation];
// 3.2 直接開始
// 這樣的話,全部操作都在當前線程(主線程)中進行操
//[operation start];
NSLog(@"touchEnd--->%@", [NSThread currentThread]);
}
注意,
main方法被調(diào)用時,屬性已經(jīng)準備就緒
目標三:如何回調(diào)?
利用系統(tǒng)提供的 CompletionBlock 屬性
// 設置完成回調(diào)
[operation setCompletionBlock:^{
NSLog(@"完成 %@", [NSThread currentThread]);
}];
- 只要設置了
CompletionBlock,當操作執(zhí)行完畢后,就會被自動調(diào)用 -
CompletionBlock既不在主線程也不在操作執(zhí)行所在線程 -
CompletionBlock無法傳遞參數(shù)
自己定義回調(diào) Block,在操作結束后執(zhí)行
block: 指向結構體的指針!塊代碼!閉包!!
-
閉包: javascript/js最先使用 :可以從函數(shù)外部訪問函數(shù)內(nèi)部的變量! --- 靈活性!
- 定義 block 類型 (返回值、接受的參數(shù)類型...)
- 執(zhí)行 block 中執(zhí)行的內(nèi)容(block 中封裝的代碼)
- 執(zhí)行或調(diào)用 block (相當于調(diào)用函數(shù))
----以上三個步驟,必須按順序進行!
可以在不同的對象中,分別設置三個步驟,只要保證順序就OK 定義屬性
/// 完成回調(diào) Block
@property (nonatomic, copy) void (^finishedBlock)(UIImage *image);
- 設置自定義回調(diào)
// 設置自定義完成回調(diào)
[op setFinishedBlock:^(UIImage *image) {
NSLog(@"finished %@ %@", [NSThread currentThread], image);
}];
- 耗時操作后執(zhí)行回調(diào)
// 判斷自定義回調(diào)是否存在
if (self.finishedBlock != nil) {
// 通常為了簡化調(diào)用方的代碼,異步操作結束后的回調(diào),大多在主線程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.finishedBlock(image對象);
}];
}
目標四:簡化操作創(chuàng)建
- 定義
類方法
/// 實例化下載圖像操作
///
/// @param URLString 圖像 URL 字符串
/// @param finished 完成回調(diào) Block
///
/// @return 下載操作實例
+ (instancetype)downloadImageOperationWithURLString:(NSString *)URLString finished:(void (^)(UIImage *image))finished;
- 實現(xiàn)方法
+ (instancetype)downloadImageOperationWithURLString:(NSString *)URLString finished:(void (^)(UIImage *))finished {
DownloadImageOperation *operation = [[DownloadImageOperation alloc] init];
operation.URLString = URLString;
operation.finishedBlock = finished;
return operation;
}
- 方法調(diào)用
// 使用類方法實例化下載操作
DownloadImageOperation *operation = [DownloadImageOperation downloadImageOperationWithURLString:@"http://www.baidu.com/img/bdlogo.png" finished:^(UIImage *image) {
NSLog(@"%@", image);
}];
// 將自定義操作添加到下載隊列,操作啟動后會執(zhí)行 main 方法
[self.downloadQueue addOperation:op];
目標五:取消操作
在關鍵節(jié)點添加
isCancelled判斷
- 添加多個下載操作
for (int i = 0; i < 10; ++i) {
NSString *urlString = [NSString stringWithFormat:@"http://www.xxx.com/%04d.png", i];
DownloadImageOperation *op = [DownloadImageOperation downloadImageOperationWithURLString:urlString finished:^(UIImage *image) {
NSLog(@"===> %@", image);
}];
// 將自定義操作添加到下載隊列,操作啟動后會執(zhí)行 main 方法
[self.downloadQueue addOperation:op];
}
- 設置隊列最大并發(fā)操作數(shù)
_downloadQueue.maxConcurrentOperationCount = 2;
- 內(nèi)存警告時取消所有操作
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
[self.downloadQueue cancelAllOperations];
}
cancelAllOperations會向隊列中的所有操作發(fā)送Cancel消息
- 調(diào)整
main方法,在關鍵節(jié)點判斷
- (void)main {
NSLog(@"%s", __FUNCTION__);
@autoreleasepool {
NSLog(@"下載圖像 %@", self.URLString);
// 模擬延時
[NSThread sleepForTimeInterval:1.0];
if (self.isCancelled) {
NSLog(@"1.--- 返回");
return;
}
// 判斷自定義回調(diào)是否存在
if (self.finishedBlock != nil) {
// 通常為了簡化調(diào)用方的代碼,異步操作結束后的回調(diào),大多在主線程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.finishedBlock(self.URLString);
}];
}
}
}
注意:如果操作狀態(tài)已經(jīng)是
Cancel,則不會執(zhí)行main函數(shù)