iOS開發之多線程技術——NSOperation篇

来源:http://www.cnblogs.com/Jepson1218/archive/2016/02/04/5180915.html
-Advertisement-
Play Games

本篇將從四個方面對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不好做)
繼續/暫停/全部取消
跨隊列設置操作的依賴關係

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • js實現的時間戳和時間日期的轉換:時間戳和時間日期的轉換是常見的操作,下麵就通過代碼實例介紹一下如何實現它們之間的相互轉換。建議事先參閱javascript中Date()構造函數參數介紹一章節。一.時間日期轉換為時間戳:現在有這麼一個時間日期:"2013/5/12 20:10:20",下麵將其轉換為
  • css實現的當滑鼠懸浮彈出說明層效果:本章節分享一段代碼實例,它是使用純css實現的滑鼠懸浮彈出說明層效果。代碼實例如下: <!DOCTYPE html> <html> <head> <meta charset=" utf-8"> <meta name="author" content="http:
  • checbox覆選框實現radio單選框的單選功能:大家知道覆選框可以一次選中多個,單選按鈕每次只能夠選中其中的一個,但是單選按鈕比較霸道,你選中以後,只能夠且必須選中其中一個,所有下麵就通過checkbox覆選框模擬實現單選按鈕的功能,但是能夠取消選中的項。代碼如下: <!DOCTYPE html
  • 原生js實現的創建和刪除元素實例代碼:在實際應用中,往往需要動態的創建和刪除指定的元素,下麵就通過代碼實例介紹一下如何實現此功能。代碼實例如下: <!DOCTYPE html> <html> <head> <meta charset=" utf-8"> <meta name="author" con
  • 絕對定位對margin外邊距的影響:關於什麼是絕對定位和外邊距這裡就不多介紹了,具體可以參閱以下兩篇文章。(1).絕對定位可以參閱CSS的絕對定位一章節。(2).外邊距可以參閱CSS的外邊距一章節。下麵就通過代碼實例介紹一下絕對定位對於margin外邊距的影響。代碼實例如下: <!DOCTYPE h
  • $.extend()和$.fn.extend()函數用法簡單介紹:標題中的兩個方法在jQuery插件開發中非常的重要,下麵通過簡單的代碼實例介紹一下它們的用法。本章節不會介紹它的原理,而是只給出它們的作用是什麼,能夠做什麼事情。jQuery.extend()可以為jQuery類添加新的方法,類似於c
  • javascript如何設置指定標簽的透明度:在實際應用中,可能需要動態的設置標簽的透明度,現在就以div為例子介紹一下如何實現此效果。代碼實例如下: <!DOCTYPE html> <html> <head> <meta charset=" utf-8"> <meta name="author"
  • body在預設情況下是具有margin外邊距的:這裡只是陳述一個事實,那就是body具有外邊距在預設情況下。代碼實例如下: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="author" content="http:/
一周排行
    -Advertisement-
    Play Games
  • C#TMS系統代碼-基礎頁面BaseCity學習 本人純新手,剛進公司跟領導報道,我說我是java全棧,他問我會不會C#,我說大學學過,他說這個TMS系統就給你來管了。外包已經把代碼給我了,這幾天先把增刪改查的代碼背一下,說不定後面就要趕鴨子上架了 Service頁面 //using => impo ...
  • 委托與事件 委托 委托的定義 委托是C#中的一種類型,用於存儲對方法的引用。它允許將方法作為參數傳遞給其他方法,實現回調、事件處理和動態調用等功能。通俗來講,就是委托包含方法的記憶體地址,方法匹配與委托相同的簽名,因此通過使用正確的參數類型來調用方法。 委托的特性 引用方法:委托允許存儲對方法的引用, ...
  • 前言 這幾天閑來沒事看看ABP vNext的文檔和源碼,關於關於依賴註入(屬性註入)這塊兒產生了興趣。 我們都知道。Volo.ABP 依賴註入容器使用了第三方組件Autofac實現的。有三種註入方式,構造函數註入和方法註入和屬性註入。 ABP的屬性註入原則參考如下: 這時候我就開始疑惑了,因為我知道 ...
  • C#TMS系統代碼-業務頁面ShippingNotice學習 學一個業務頁面,ok,領導開完會就被裁掉了,很突然啊,他收拾東西的時候我還以為他要旅游提前請假了,還在尋思為什麼回家連自己買的幾箱飲料都要叫跑腿帶走,怕被偷嗎?還好我在他開會之前拿了兩瓶芬達 感覺感覺前面的BaseCity差不太多,這邊的 ...
  • 概述:在C#中,通過`Expression`類、`AndAlso`和`OrElse`方法可組合兩個`Expression<Func<T, bool>>`,實現多條件動態查詢。通過創建表達式樹,可輕鬆構建複雜的查詢條件。 在C#中,可以使用AndAlso和OrElse方法組合兩個Expression< ...
  • 閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...