本篇將從四個方面對iOS開發中使用到的NSOperation技術進行講解: 一、什麼是NSOperation 二、我們為什麼使用NSOperation 三、在實際開發中如何使用NSOperation 1、自定義NSOperation 2、NSOperation的基本使用 3、NSOperation實
本篇將從四個方面對iOS開發中使用到的NSOperation技術進行講解:
一、什麼是NSOperation
二、我們為什麼使用NSOperation
三、在實際開發中如何使用NSOperation
1、自定義NSOperation
2、NSOperation的基本使用
3、NSOperation實現線程間通信
1)利用代理進行消息傳遞
2)利用通知實現消息傳遞
3)利用block進行消息傳遞
四、與GCD比較
一、什麼是NSOperation
NSOperation是一個抽象的基類,表示一個獨立的計算單元,可以為子類提供有用且線程安全的建立狀態,優先順序,依賴和取消等操作。系統已經給我們封裝了NSBlockOperation和NSInvocationOperation這兩個實體類。使用起來也非常簡單,不過我們更多的使用是自己繼承並定製自己的操作。
二、我們為什麼使用NSOperation
在iOS開發中,為了提升用戶體驗,我們通常會將操作耗時的操作放在主線程之外的線程進行處理。對於正常的簡單操作,我們更多的是選擇代碼更少的GCD,讓我們專註於自己的業務邏輯開發。NSOperation在ios4後也基於GCD實現,但是相對於GCD來說可控性更強,並且可以加入操作依賴。
三、在實際開發中如何使用NSOperation
1、自定義NSOperation
在實際開發中,系統提供的NSOperation可能無法滿足我們的需求,這時,我們就需要自定義我們自己的NSOperation
@interface ViewController () @property (nonatomic, weak) IBOutlet UIImageView *imageView; @end @implementation ViewController - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"touchesBegan------%@", [NSThread currentThread]); if (!self.imageView.image) { // 避免重覆下載,增強用戶體驗 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue setMaxConcurrentOperationCount:6]; MyOperation *myO = [[MyOperation alloc] init]; myO.imageView = self.imageView; [queue addOperation:myO]; } NSLog(@"end"); } // 自定義NSOperation @interface MyOperation : NSOperation @property (nonatomic, strong) UIImageView *imageView; @end // 實現自定義NSOperation @implementation MyOperation - (void)main { NSLog(@"%s----%@", __func__, [NSThread currentThread]); UIImage *image = [self downLoadImage:@"http://g.hiphotos.baidu.com/image/pic/item/f31fbe096b63f624cd2991e98344ebf81b4ca3e0.jpg"]; dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"%@======%@", image, [NSThread currentThread]); self.imageView.image = image; }); } - (UIImage *)downLoadImage:(NSString *)urlString { NSLog(@"%s----%@",__func__, [NSThread currentThread]); NSURL *url = [NSURL URLWithString:urlString]; NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; NSLog(@"圖片下載完成"); return image; } @end
2、NSOperation的基本使用
#pragma mark #pragma mark - NSOperation高級操作1 - (void)highLevelTest1 { /** NSOperation 相對於 GCD 來說,增加了以下管理線程的功能: 1.NSOperation可以添加操作依賴:保證操作的執行順序! --> 和GCD中將任務添加到一個串列隊列中是一樣的!一個串列隊列會對應一條線程 GCD 中的按順序執行(串列隊列) ---> 串列執行 添加操作依賴之後,系統有可能串列執行保證任務的執行順序,還有可能綠色線程同步技術,保證任務執行順序 */ NSInvocationOperation *inO = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil]; NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"block1======%@", [NSThread currentThread]); }]; NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"block2======%@", [NSThread currentThread]); }]; NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"block3======%@", [NSThread currentThread]); }]; /** 四個操作都是耗時操作,並且要求按順序執行,操作2是UI操作 添加操作依賴的註意點 1.一定要在將操作添加到操作隊列中之前添加操作依賴 2.不要添加迴圈依賴 優點:對於不同操作隊列中的操作,操作依賴依然有效 */ // 1.一定要在將操作添加到操作隊列中之前添加操作依賴 [block2 addDependency:block1]; [block3 addDependency:block2]; [inO addDependency:block3]; // 2.不要添加迴圈依賴 [block1 addDependency:block3]; [[[NSOperationQueue alloc] init] addOperation:block1]; [[[NSOperationQueue alloc] init] addOperation:block2]; [[[NSOperationQueue alloc] init] addOperation:block3]; [[NSOperationQueue mainQueue] addOperation:inO]; } - (void)test { NSLog(@"測試%s-----%@", __func__, [NSThread currentThread]); } #pragma mark #pragma mark - NSOperation高級操作2 - (void)highLevelTest2 { /** NSOperation高級操作 應用場景:提高用戶體驗第一,當用戶操作時,取消一切跟用戶當前操作無關的進程,提升流暢度 1.添加操作依賴 2.管理操作:重點!是操作隊列的方法 2.1暫停/恢復 取消 操作 2.2開啟合適的線程數量!(最多不超過6條) 一般開發的時候,會將操作隊列設置成一個全局的變數(屬性) */ NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"---------"); }]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperationWithBlock:^{ [self test]; }]; [queue addOperation:block1]; // 1.暫停操作 開始滾動的時候 [queue setSuspended:YES]; // 2.恢復操作 滑動結束的時候 [queue setSuspended:NO]; // 3.取消所有操作 接收到記憶體警告 [queue cancelAllOperations]; // 3.1補充:取消單個操作調用該NSOperation的cancel方法 [block1 cancel]; // 4.設置線程最大併發數,開啟合適的線程數量 實例化操作隊列的時候 [queue setMaxConcurrentOperationCount:6]; /** 遇到併發編程,什麼時候選擇 GCD, 什麼時候選擇NSOperation 1.簡單的開啟線程/回到主線程,選擇GCD:效率更高,簡單 2.需要管理操作(考慮到用戶交互!)使用NSOperation */ } #pragma mark #pragma mark - NSOperation簡單操作 - (void)BaseTest { // 1.實例化操作對象 NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"blockOperation1---------%@", [NSThread currentThread]); }]; // 往當前操作中追加操作 [blockOperation1 addExecutionBlock:^{ NSLog(@"addblockOperation1.1-----%@", [NSThread currentThread]); }]; [blockOperation1 addExecutionBlock:^{ NSLog(@"addblockOperation1.2-----%@", [NSThread currentThread]); }]; /** 當 NSBlockOperation中的任務數 > 1 之後,無論是將操作添加到主線程還是在主線程直接執行 start, NSBlockOperation中的任務執行順序都不確定,執行線程也不確定! 一般在開發的時候,要避免向 NSBlockOperation 中追加任務! 如果任務都是在子線程中執行,並且不需要保證執行順序!可以直接追加任務 */ NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"blockOperation2---------%@", [NSThread currentThread]); }]; NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"blockOperation3---------%@", [NSThread currentThread]); }]; // 將操作添加到非主隊列中 // NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // // [queue addOperation:blockOperation1]; // [queue addOperation:blockOperation2]; // [queue addOperation:blockOperation3]; // 將操作添加到主隊列中 [[NSOperationQueue mainQueue] addOperation:blockOperation1]; [[NSOperationQueue mainQueue] addOperation:blockOperation2]; [[NSOperationQueue mainQueue] addOperation:blockOperation3]; }
3、NSOperation實現線程間通信
1)利用代理進行消息傳遞
@interface ViewController ()<MyOperationDelegate> @property (weak, nonatomic) IBOutlet UIImageView *imageView; @end @implementation ViewController - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"%s-----%@", __func__, [NSThread currentThread]); NSString *urlString = @"http://h.hiphotos.baidu.com/image/pic/item/30adcbef76094b366b2389d7a4cc7cd98d109d53.jpg"; // 1.創建操作對象 MyOperation *myO = [[MyOperation alloc] init]; myO.delegate = self; // 3.告訴 myO 下載哪一張圖片 myO.urlString = urlString; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue setMaxConcurrentOperationCount:6]; // 4.將操作添加到操作隊列中 [queue addOperation:myO]; NSLog(@"end"); } #pragma mark #pragma mark - 實現操作的代理方法 - (void)downImage:(UIImage *)image { NSLog(@"%s------%@", __func__, [NSThread currentThread]); self.imageView.image = image; } @protocol MyOperationDelegate <NSObject> - (void)downImage:(UIImage *)image; @end @interface MyOperation : NSOperation @property (nonatomic, copy) NSString *urlString; @property (nonatomic, weak) id<MyOperationDelegate> delegate; @end @implementation MyOperation #pragma mark #pragma mark - 重寫NSOperation的main方法 // 當把自定義的操作添加到操作隊列中,或者直接調用操作的 start 方法後,都會自動來執行main 方法中的內容 - (void)main { NSLog(@"%s------%@", __func__, [NSThread currentThread]); UIImage *image = [self downLoadImageSubThread]; // 回到主線程執行代理方法 dispatch_async(dispatch_get_main_queue(), ^{ if ([self.delegate respondsToSelector:@selector(downImage:)]) { [self.delegate downImage:image]; } }); } #pragma mark #pragma mark - 下載網路圖片的方法 - (UIImage *)downLoadImageSubThread { NSURL *url = [NSURL URLWithString:self.urlString]; NSData *data = [NSData dataWithContentsOfURL:url]; NSLog(@"下載完成"); return [UIImage imageWithData:data]; } @end
2)利用通知實現消息傳遞
@interface ViewController () @property (nonatomic, weak) IBOutlet UIImageView *imageView04; @end @implementation ViewController - (void)viewDidLoad { NSLog(@"%s-----%@", __func__, [NSThread currentThread]); // 註冊通知觀察者 // object:nil ,nil可以保證觀察者接收任意類型的通知! [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setUpImageWithNotify:) name:@"FinishDownLoadImage" object:nil]; } - (void)dealloc { // 通知用完之後需要移除 [[NSNotificationCenter defaultCenter] removeObserver:self]; } // 接收到通知之後,執行的方法 noti:接收到的通知 - (void)setUpImageWithNotify:(NSNotification *)noti { NSLog(@"%s-----%@", __func__, [NSThread currentThread]); // 顯示圖片 self.imageView04.image = noti.object; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"%s-----%@", __func__, [NSThread currentThread]); // 1.創建操作隊列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue setMaxConcurrentOperationCount:6]; // 2.告訴操作下載哪張圖片 MyOperation *myO = [[MyOperation alloc] init]; myO.urlString = @"http://h.hiphotos.baidu.com/image/pic/item/72f082025aafa40f7c884d31af64034f79f0198b.jpg"; // 3.將操作添加到操作隊列,會自動調用操作中的main 方法 [queue addOperation:myO]; NSLog(@"end"); } /**************************************************/ // 自定義NSOperation類 @interface MyOperation : NSOperation @property (nonatomic, copy) NSString *urlString; @end // 實現自定義NSOperation類 @implementation MyOperation - (void)main { NSLog(@"%s-----%@", __func__, [NSThread currentThread]); // 在子線程下載好圖片後再傳給主線程 UIImage *image = [self downLoadImage:self.urlString]; // 圖片下載完畢之後,利用通知告訴控制器,圖片下載結束,並且將下載好的圖片傳遞給控制器 // 在主線程發送通知 dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:@"FinishDownLoadImage" object:image]; }); } - (UIImage *)downLoadImage:(NSString *)urlString { NSURL *url = [NSURL URLWithString:urlString]; NSData *data = [NSData dataWithContentsOfURL:url]; return [UIImage imageWithData:data]; }
3)利用block進行消息傳遞
@interface ViewController () @property (weak, nonatomic) IBOutlet UIImageView *imageView; @end @implementation ViewController - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSLog(@"%s----%@",__func__, [NSThread currentThread]); NSString *urlString = @"http://h.hiphotos.baidu.com/image/pic/item/72f082025aafa40f7c884d31af64034f79f0198b.jpg"; // 2.創建操作 MyOperation *mo = [[MyOperation alloc] init]; mo.urlString = urlString; mo.view = self.view; [mo downLoadWebImageWithBlock:^(UIImage *image) { self.imageView.image = image; }]; [mo setCompletionBlock:^{ NSLog(@"圖片下載完成"); }]; NSLog(@"end"); } /*********************************************************/ // 自定義NSOperation類 typedef void(^downLoadBlock)(UIImage *image); @interface MyOperation : NSOperation @property (nonatomic, strong) UIView *view; @property (nonatomic, copy) NSString *urlString; @property (nonatomic, copy) downLoadBlock block; - (void)downLoadWebImageWithBlock:(downLoadBlock)blk; @end // 實現自定義NSOperation類 @implementation MyOperation #pragma mark #pragma mark - 實現block方法,方便直接回車調用 - (void)downLoadWebImageWithBlock:(downLoadBlock)blk { if (blk) { self.block = blk; dispatch_async(dispatch_get_global_queue(0, 0), ^{ UIImage *image = [self downLoadImage:self.urlString]; dispatch_async(dispatch_get_main_queue(), ^{ if (self.block) { self.block(image); } }); }); } } #pragma mark #pragma mark - 下載圖片的方法 - (UIImage *)downLoadImage:(NSString *)strUrl { NSLog(@"%s----%@",__func__, [NSThread currentThread]); NSURL *url = [NSURL URLWithString:strUrl]; NSData *data = [NSData dataWithContentsOfURL:url]; NSLog(@"下載完成"); return [UIImage imageWithData:data]; } @end
四、與GCD比較
GCD:
將任務(block)添加到隊列(串列/併發/主隊列),並且指定任務執行的函數(同步/非同步)
GCD是底層的C語言構成的API
iOS 4.0 推出的,針對多核處理器的併發技術
在隊列中執行的是由 block 構成的任務,這是一個輕量級的數據結構
要停止已經加入 queue 的 block 需要寫複雜的代碼
需要通過 Barrier 或者同步任務設置任務之間的依賴關係
只能設置隊列的優先順序
高級功能:
一次性 once
延遲操作 after
調度組
NSOperation:
核心概念:把操作(非同步)添加到隊列(全局的併發隊列)
OC 框架,更加面向對象,是對 GCD 的封裝
iOS 2.0 推出的,蘋果推出 GCD 之後,對 NSOperation 的底層全部重寫
Operation作為一個對象,為我們提供了更多的選擇
可以隨時取消已經設定要準備執行的任務,已經執行的除外
可以跨隊列設置操作的依賴關係
可以設置隊列中每一個操作的優先順序
高級功能:
最大操作併發數(GCD不好做)
繼續/暫停/全部取消
跨隊列設置操作的依賴關係