iOS多線程NSThread/GCD/NSOperation區別和使用


概述:

1.NSThread 一般用做調試用,需要程序員管理生命周期,開發中較少使用。

2.GCD(iOS 4.0) 由系統管理,開發中使用的很多。

3.NSOperation(iOS 2.0) 基於GCD的OC封裝,開發中使用的較多。

  • GCD(grand central dispatch)

核心概念:同步/異步,全局隊列/主隊列

 全局隊列:
 {
    同步:不開
    異步:開N條
 }
 
 主隊列(奇葩):
 {
        同步:卡死,不要用
        異步:不開,因為他有主線程
 }
 
 /**
    開不開線程線程由任務是同步還是異步
        同步:打死都不開
        異步:除了主隊列,都開,開多少條,由我們的隊列的類型來決定
 */

使用方式1:異步下載圖片,回到主線程更新UI

    //一般下載,可能會下載多個
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"downLoad----%@",[NSThread currentThread]);
        //去做耗時間的操作
        
        //下載圖像...最后得到一個UIImage
        
        //去主線程更新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"updateUI----%@",[NSThread currentThread]);
        });
        
    });

使用方式2:線程安全-設置依賴關系(例如你異步下載20張圖片寫入一個數組中,為了保證效率所以數組為非線程安全的,如果解決多線程同時訪問的問題)

解決辦法是間接通過GCD的阻塞(dispatch_barrier_async)和同步

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    //創建一個並發的隊列
    /**
        注意:如果你用了dispatch_barrier_async必須要用我們自己創建的並發隊列,而不能用全局隊列
     */
    _concurrentQueue = dispatch_queue_create("com.yun", DISPATCH_QUEUE_CONCURRENT);
    
    //循環去請求20個圖片資源
    for (int i=0; i<20; ++i) {
        [self loadPic:i];
    }
}

- (void)loadPic:(int)i{
    //模擬網絡,通過URL去訪問Bundle里面的圖片
    dispatch_async(_concurrentQueue, ^{
        [NSThread sleepForTimeInterval:1.0];
        
        NSString *fileName = [NSString stringWithFormat:@"%02d.jpg",i%10+1];
        
        //1.URL
        NSURL *url =[[NSBundle mainBundle] URLForResource:fileName withExtension:nil];
        
        //2.去Bundle里面加載我們的圖片二進制數據
        NSData *data =[NSData dataWithContentsOfURL:url];
        
        //3.將圖片的二進制數據,轉成UIImage對象
        UIImage *image = [UIImage imageWithData:data];
        
        
        //4.將我們的圖像添加到photoList數組中去
        NSLog(@"%@----%d",[NSThread currentThread],i);
        
        dispatch_barrier_async(_concurrentQueue, ^{
            [self.photoList addObject:image];
        });
    });
}

 

擴展:

如果想在dispatch_queue中所有的任務執行完成后在做某種操作,在串行隊列中,可以把該操作放到最后一個任務執行完成后繼續,但是在並行隊列中怎么做呢。這就有dispatch_group 成組操作。

dispatch_queue_t dispatchQueue = dispatch_queue_create("ted.queue.next", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t dispatchGroup = dispatch_group_create();
dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
NSLog(@"dispatch-1");
});
dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
NSLog(@"dspatch-2");
});
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
NSLog(@"end");
});

  

上面的 log1 和log2輸出順序不定,因為是在並行隊列上執行,當並行隊列全部執行完成后,最后到main隊列上執行一個操作,保證“end”是最后輸出。 另外,這里也可以不用創建自己的並行隊列,用全局的global,那個也是個並行隊列. dispatch_get_gloable_queue(0,0);

 

常用方式3:單例(dispatch_once)的創建和延遲執行(dispatch_after)

Tips:

在NSOperationQueue中,我們可以隨時取消已經設定要准備執行的任務(當然,已經開始的任務就無法阻止了),而GCD沒法停止已經加入queue的block(其實是有的,但需要許多復雜的代碼);
GCD原生並不支持取消操作。

dispatch_suspend函數也只能暫停開啟新的未執行的block,已經處於執行中的block是無法暫停的。

 

 

  • NSOperation(抽象類不能直接使用,方式一:使用NSInvocationOperation 和 NSBlockOperation,常用方式二:自定義NSOperation即寫一個子類繼承自它需要重寫它的main方法就可以把子類的對象放入NSOperationQueue隊列中直接使用了)和NSOperationQueue

常見用法一:添加異步任務之間的依賴

/**
    1.登錄
    2.付費
    3.下載
    4.通知用戶
 
    1.依賴的代碼,必須放在添加任務前
    2.不要造成循環依賴,iOS7之前是直接崩,iOS8,不調度了
 */
- (void)depencyDemo{
    NSLog(@"%s",__FUNCTION__);
    
    NSBlockOperation *login = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@----login",[NSThread currentThread]);
    }];
    
    NSBlockOperation *notifyUser = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@----notifyUser",[NSThread currentThread]);
    }];
    
    NSBlockOperation *downLoad = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@----downLoad",[NSThread currentThread]);
    }];
    
    NSBlockOperation *pay = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@----pay",[NSThread currentThread]);
    }];
    
    NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
    
    //GCD,同步,串行隊列
    //NSOperation,addDependency就是表示我這個任務,要依賴於哪個任務執行完
    [downLoad addDependency:pay];
    [pay addDependency:login];
    [notifyUser addDependency:downLoad];
    //[login addDependency:downLoad];//???
    
    //將任務添加到`隊列`中去
//    [mainQueue addOperation:login];
//    [mainQueue addOperation:notifyUser];
//    [self.concurrentQueue addOperation:downLoad];
//    [mainQueue addOperation:pay];
    // 注意download是放在異步線程里的
     [self.concurrentQueue addOperation:downLoad];
    
    //YES,同步,       主隊列千萬不要和同步搞在一起
    [mainQueue addOperations:@[login,notifyUser,pay] waitUntilFinished:NO];
    
}

打印結果如下:

[ViewController depencyDemo]
<NSThread: 0x7fa88a707320>{number = 1, name = main}----login
<NSThread: 0x7fa88a707320>{number = 1, name = main}----pay
<NSThread: 0x7fa88a4bf850>{number = 2, name = (null)}----downLoad
<NSThread: 0x7fa88a707320>{number = 1, name = main}----notifyUser

用法二:支持KVO可以觀察操作狀態(正在執行、是否結束、是否取消)

用法三:支持設置最大並發數(省電)而GCD不可以

用法四:異步耗時操作主隊列更新UI

    //1.創建一個並發隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    //2.創建任務,並將其添加到`並發隊列中`
    [queue addOperationWithBlock:^{
        NSLog(@"login===>%@",[NSThread currentThread]);
        
        [NSThread sleepForTimeInterval:2.0];
        
        //去主線程更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            [NSThread sleepForTimeInterval:3.0];
            //更新UI的代碼
            NSLog(@"%@===>",[NSThread currentThread]);
        }];
    }];

 

 

有新的發現會繼續來完善....

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM