iOS開發-多線程學習

iOS開發中常用的幾種多線程方案,簡單做個小結,方便日后查閱。

  • Pthreads
  • NSThead
  • GCD
  • NSOperation & NSOpeartionQueue

Pthreads#

這種方式不用介紹(我也不太會使用),一般ios開發里也用不上,這是在很多操作系統中都通用的。使用方法大概如下:
#import <pthread.h>
創建線程并執行任務:
void *run(void *data){
for (int i = 0; i<1000; i++) {
NSLog(@"touchesBegan:%d-----%@", i, [NSThread currentThread]);
}
return NULL;
}

  -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
  {
  // 創建線程
    pthread_t myRestrict;
    pthread_create(&myRestrict, NULL, run, NULL);
  }

通過log可以看到:

2016-03-08 18:27:04.936 testPthread[4859:1637201] touchesBegan:0-----{number = 3, name = (null)}
2016-03-08 18:27:04.937 testPthread[4859:1637201] touchesBegan:1-----{number = 3, name = (null)}
2016-03-08 18:27:04.937 testPthread[4859:1637201] touchesBegan:2-----{number = 3, name = (null)}
2016-03-08 18:27:04.937 testPthread[4859:1637201] touchesBegan:3-----{number = 3, name = (null)}
2016-03-08 18:27:04.937 testPthread[4859:1637201] touchesBegan:4-----{number = 3, name = (null)}

這種方式雖然創建了一個線程,但并沒有銷毀,我們需要手動管理線程的生命周期。隨意感受一下就好了。。。

NSThead#

優點:NSThread 比其他幾個輕量級。
缺點:需要自己管理線程的生命周期,線程同步。線程同步對數據的加鎖會有一定的系統開銷。

創建和啟動線程的3種方式:###

1.先創建后啟動:####

// 創建線程
NSThread *thread = [[NSThread alloc] initWithTarget:self     selector:@selector(download:) object:@"http://b.png"];
thread.name = @"下載線程";
// 啟動線程(調用self的download方法)
[thread start];

selector :線程執行的方法,這個selector只能有一個參數,而且不能有返回值。
target :selector消息發送的對象
argument:傳輸給target的唯一參數,也可以是nil

2.創建并自動啟動線程:####

 [NSThread detachNewThreadSelector:@selector(download:) toTarget:self withObject:@"http://a.jpg"];

3.使用NSObject方法創建線程并自動啟動:####

[self performSelectorInBackground:@selector(download:) withObject:@"http://c.gif"];

其他常見方法:

//獲得當前線程
+(NSThread *)currentThread;

 //獲得主線程   
 +(NSThread *)mainThread;

 //睡眠(暫停)線程  
 +(void)sleepUntilDate:(NSDate *)date;    
 +(void)sleepForTimeInterval:(NSTimeInterval)ti;

//設置和獲取線程名稱    
 -(void)setName:(NSString *)n;   
 -(NSString *)name;

GCD#

Grand Central Dispatch 簡稱(GCD)是蘋果公司開發的技術,以優化的應用程序支持多核心處理器和其他的對稱多處理系統的系統。這建立在任務并行執行的線程池模式的基礎上的。它首次發布在Mac OS X 10.6 ,iOS 4及以上也可用。GCD的工作原理是:讓程序平行排隊的特定任務,根據可用的處理資源,安排他們在任何可用的處理器核心上執行任務。它是蘋果為多核的并行運算提出的解決方案,所以會自動合理地利用更多的CPU內核(比如雙核、四核),最重要的是它會自動管理線程的生命周期(創建線程、調度任務、銷毀線程),完全不需要我們管理,我們只需要告訴干什么就行。

任務和隊列###

任務:

需要執行什么操作,一個任務可以是一個函數(function)或者是一個block。任務有兩種執行方式:同步執行異步執行,它們區別在于是否會阻塞當前線程,直到任務執行完。如果是 同步(sync) 操作,它會阻塞當前線程并等待 Block 中的任務執行完畢,然后當前線程才會繼續往下運行。如果是 異步(async)操作,當前線程會直接往下執行,它不會阻塞當前線程。

創建任務方式:

同步任務:會阻塞當前線程,并且不具備開啟新線程的能力。

  dispatch_sync(<#queue#>, ^{  
    //code here  
  });

異步任務:不會阻塞當前線程,并且具有開啟新線程的能力。

dispatch_async(<#queue#>, ^{ 
 //code here
});

隊列:

存放任務。有串行隊列和并行隊列兩種。
串行隊列:GCD中的FIFO隊列稱為dispatch queue,它可以保證先進來的任務先得到執行,然后再取下一個,一個接著一個執行。
并行隊列:放到并行隊列的任務可以并發執行。不過需要注意,GCD 會根據系統資源控制并行的數量,所以如果任務很多,它并不會讓所有任務同時執行。

創建隊列方式

主隊列:這是一個特殊的串行隊列,主要用于刷新UI界面,處理UI控件的事件。如下:
dispatch_queue_t queue = dispatch_get_main_queue();

自己創建的隊列: 自己可以創建串行隊列,也可以創建 并行隊列。它有兩個參數:
第一個參數:標識符,用于debug的時候標識唯一的隊列。
第二個參數:用來表示創建的隊列是串行的還是并行的,傳入 DISPATCH_QUEUE_SERIALNULL 表示創建串行隊列。傳入 DISPATCH_QUEUE_CONCURRENT 表示創建并行隊列。如下:

 //串行隊列   
 dispatch_queue_t queue =dispatch_queue_create("serial.testQueue", NULL);

 dispatch_queue_t queue = dispatch_queue_create("serial.testQueue", DISPATCH_QUEUE_SERIAL); 

//并行隊列    
dispatch_queue_t queue = dispatch_queue_create("concurrent.testQueue", DISPATCH_QUEUE_CONCURRENT);

全局并發隊列: 可以讓任務并發執行,只要是并行任務一般都加入到這個隊列中。

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

下面的例子能更好的理解同步異步和各種隊列的使用:
例1:

  //sync -- 主隊列(不能用---會卡死) 
-(void)testSyncMainQueue{
    NSLog(@"download之前----%@",[NSThread currentThread]);    
  // 1.主隊列(添加到主隊列中的任務,都會自動放到主線程中去執行)  
    dispatch_queue_t queue = dispatch_get_main_queue();        
  // 2.異步執行    
    dispatch_sync(queue, ^{        
    NSLog(@"-----download1---%@", [NSThread currentThread]);    
    });    
    dispatch_sync(queue, ^{        
    NSLog(@"-----download2---%@", [NSThread currentThread]);    
    });    
  NSLog(@"download之后------");
} 

打印結果:

2016-03-09 10:37:47.450 testGCD[5725:2260551] download之前----{number = 1, name = main}

只打印了第一句后主線程就被卡死了,什么界面操作都做不了。這是因為dispatch_sync同步任務會阻塞當前的主線程,然后把block中的任務放到主隊列queue當中,可是添加到主隊列中的任務,都會自動放到主線程中去執行,但是主線程已被阻塞,所以block中download無法執行,dispatch_sync就會一直阻塞主線程,造成死鎖。

例2:
- (void)testAsyncSerialQueue{
// 1.創建一個串行隊列
dispatch_queue_t queue = dispatch_queue_create("testAsync.SerialQueue", NULL);

  NSLog(@"download之前----%@",[NSThread currentThread]);   
 // 2.異步執行    
  dispatch_async(queue, ^{                
    NSLog(@"async download之前----%@",[NSThread currentThread]); 
    dispatch_sync(queue, ^{            
    NSLog(@"sync download ----%@",[NSThread currentThread]);       
 });               
   NSLog(@"async download 之后----%@",[NSThread currentThread]);      
});   

  dispatch_async(queue, ^{         
    NSLog(@"async download2----%@",[NSThread currentThread]);   
  });   
 dispatch_async(queue, ^{       
   NSLog(@"async download3----%@",[NSThread currentThread]);    
});    
  NSLog(@"async download 之后----%@",[NSThread currentThread]);
}

打印結果:

2016-03-09 11:00:27.419 testGCD[5876:2284715] download之前----{number = 1, name = main}
2016-03-09 11:00:27.420 testGCD[5876:2284748] sync download之前----{number = 2, name = (null)}
2016-03-09 11:00:27.420 testGCD[5876:2284715] async download 之后----{number = 1, name = main}

可以看到sync download----async download之后----還有async download2----async download3----這幾個沒有被打印出來。這是為啥?

分析:

  1. 首先必須明白,dispatch_queue_create創建一個新隊列queue, 參數DISPATCH_QUEUE_SERIALNULL 表示創建的這是一個串行隊列。
  2. 接著打印download之前----正常;
  3. dispatch_async異步執行,當前線程不會被阻塞,且具備開啟新線程能力,所以同時有了兩條線程。一條當前線程打印出了async download 之后----,正常。另外一條線程執行block里的任務,打印sync download之前----,也是正常。因為這兩條線程是并行,所以并不會相互影響,順序前后也不一定。
  4. 之后dispatch_sync同步執行,同步任務會阻塞當前所在的線程,直到sync里的任務執行完畢后才會繼續往下。然后與例1相似,sync把block中的任務放到隊列queue當中,可是queue是一個串行隊列,串行隊列是任務一個接著一個執行,一次只能執行一個任務,所以sycn的block里的任務就必須等到前一個任務執行完畢,可是,前一個正在執行的任務正是被sync阻塞的那個。于是這里又出現了死鎖現象。sycn所在的線程就被卡死了。sync download----sync download之后----這兩句代碼也就不會被打印出來了。明白不?后面的兩個dispatch_async任務同樣道理。

例3:

  -(void)testAsyncGlobalQueue{    
    // 并發隊列    
    dispatch_queue_t queue       =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);        
    //異步 執行    
    dispatch_async(queue, ^{        
      NSLog(@"-----download1---%@", [NSThread currentThread]);    
    });   
    dispatch_async(queue, ^{        
      NSLog(@"-----download2---%@", [NSThread currentThread]);   
   });    
    dispatch_async(queue, ^{        
    NSLog(@"-----download3---%@", [NSThread currentThread]);   
   });    
    dispatch_async(queue, ^{        
    NSLog(@"-----download4---%@", [NSThread currentThread]);   
   });   
    dispatch_async(queue, ^{       
     NSLog(@"-----download5---%@", [NSThread currentThread]);
    });
}

打印結果:

2016-03-09 11:49:44.439 testGCD[6039:2341953] -----download5---{number = 6, name = (null)}
2016-03-09 11:49:44.439 testGCD[6039:2341800] -----download4---{number = 5, name = (null)}

2016-03-09 11:49:44.439 testGCD[6039:2341755] -----download2---{number = 2, name = (null)}
2016-03-09 11:49:44.439 testGCD[6039:2341756] -----download1---{number = 3, name = (null)}
2016-03-09 11:49:44.439 testGCD[6039:2341799] -----download3---{number = 4, name = (null)}

從這個結果可以看出,異步執行任務,一般同時開啟多條線程,且彼此之間不會相互影響,并發執行,哪條線程先后執行都不一定。

例4:

 -(void)testSyncGlobalQueue{    
  // 全局并發隊列    
   dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
 // 同步執行    
   dispatch_sync(queue, ^{        
    NSLog(@"-----download1---%@", [NSThread currentThread]);    
  });    
   dispatch_sync(queue, ^{        
      NSLog(@"-----download2---%@", [NSThread currentThread]);    
   });    
  dispatch_sync(queue, ^{        
      NSLog(@"-----download3---%@", [NSThread currentThread]);    
  });    
  dispatch_sync(queue, ^{        
      NSLog(@"-----download4---%@", [NSThread currentThread]);   
 });    
  dispatch_sync(queue, ^{        
    NSLog(@"-----download5---%@", [NSThread currentThread]);    
  });
}

打印結果:

2016-03-09 12:07:51.493 testGCD[6150:2359827] -----download1---{number = 1, name = main}
2016-03-09 12:07:51.494 testGCD[6150:2359827] -----download2---{number = 1, name = main}
2016-03-09 12:07:51.494 testGCD[6150:2359827] -----download3---{number = 1, name = main}
2016-03-09 12:07:51.494 testGCD[6150:2359827] -----download4---{number = 1, name = main}
2016-03-09 12:07:51.494 testGCD[6150:2359827] -----download5---{number = 1, name = main}

可以看出,同步任務不會創建新的線程,串行執行一次只能執行一個任務,一個接著一個,按順序執行。并且這個全局并發隊列失去了并發功能。

例5:

-(void)testSyncSerialQueue{    
  //串行隊列    
    dispatch_queue_t queue = dispatch_queue_create("testSync.SerialQueue", NULL);        
  //同步執行    
    dispatch_sync(queue, ^{       
         NSLog(@"-----download1---%@", [NSThread currentThread]); 
     });  
    dispatch_sync(queue, ^{      
        NSLog(@"-----download2---%@", [NSThread currentThread]);  
    });  
    dispatch_sync(queue, ^{       
       NSLog(@"-----download3---%@", [NSThread currentThread]);  
    });    
    dispatch_sync(queue, ^{      
      NSLog(@"-----download4---%@", [NSThread currentThread]);    
    });    
    dispatch_sync(queue, ^{        
      NSLog(@"-----download5---%@", [NSThread currentThread]);          
    });
 }

打印結果:

2016-03-09 12:20:13.188 testGCD[6210:2373033] -----download1---{number = 1, name = main}
2016-03-09 12:20:13.189 testGCD[6210:2373033] -----download2---{number = 1, name = main}
2016-03-09 12:20:13.189 testGCD[6210:2373033] -----download3---{number = 1, name = main}
2016-03-09 12:20:13.189 testGCD[6210:2373033] -----download4---{number = 1, name = main}
2016-03-09 12:20:13.189 testGCD[6210:2373033] -----download5---{number = 1, name = main}

可以看出,同步任務不會創建新的線程,串行隊列執行一次只能執行一個任務,一個接著一個,按順序執行。

隊列組#####

dispatch_group_async可以實現監聽一組任務是否完成,完成后得到通知執行其他的操作。這個方法很有用,比如你執行兩個下載任務,當兩個任務都下載完成后你才通知界面說完成的了。

  -(void)testGCDQueueGroup{    
  {        
    // 1.隊列組        
    dispatch_group_t group = dispatch_group_create();        
    // 創建隊列        
  dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);                
   
    // 2.使用隊列組的異步方法,下載庫里的圖片        
   __block UIImage *image1 = nil;   

   dispatch_group_async(group, queue, ^{            
       NSURL *url1 = [NSURL URLWithString:@"http://i2.hoopchina.com.cn/u/1306/04/318/17056318/62affa38_530x.jpg"];           
       NSData *data1 = [NSData dataWithContentsOfURL:url1];                    
      image1 = [UIImage imageWithData:data1];       
  });  
         
   // 3.使用隊列組的異步方法,下載百度的logo       
   __block UIImage *image2 = nil;        
    dispatch_group_async(group, queue, ^{            
        NSURL *url2 = [NSURL URLWithString:@"http://su.bdimg.com/static/superplus/img/logo_white_ee663702.png"];            
        NSData *data2 = [NSData dataWithContentsOfURL:url2];        
        image2 = [UIImage imageWithData:data2];        
    });    
        
  // 4.合并圖片      
  dispatch_group_notify(group, queue, ^{   
     
  // 開啟一個位圖上下文     
   UIGraphicsBeginImageContextWithOptions(image1.size, NO, 0.0);                       
   // 繪制第1張圖片            
  CGFloat image1W = image1.size.width;           
  CGFloat image1H = image1.size.height;           
  [image1 drawInRect:CGRectMake(0, 0, image1W, image1H)];                                  

  // 繪制第2張圖片           
 CGFloat image2W = image2.size.width * 0.5;           
 CGFloat image2H = image2.size.height * 0.5;          
 CGFloat image2Y = image1H - image2H;            
  [image2 drawInRect:CGRectMake(0, image2Y, image2W, image2H)];                        

    // 得到上下文中的圖片            
    UIImage *fullImage = UIGraphicsGetImageFromCurrentImageContext();                       
 // 結束上下文            
  UIGraphicsEndImageContext();                        
// 5.回到主線程顯示圖片      
    dispatch_async(dispatch_get_main_queue(), ^{ 
      self.imageView.image = fullImage;            
    });       
   });    
  }
}

這個例子就是一個簡單的使用方法,在隊列里分別下載兩張圖片,等兩張圖片下載完成后,合并成一張圖片。保證執行完組里面的所有任務之后,再執行notify函數里面的block。最后再回到主線程,更新界面并顯示出來。

還有幾點從其他文章看到,貼一下:

dispatch_barrier_async(queue, ^{ });這個方法重點是你傳入的 queue,當你傳入的 queue 是通過 DISPATCH_QUEUE_CONCURRENT 參數自己創建的 queue 時,這個方法會阻塞這個 queue(注意是阻塞 queue ,而不是阻塞當前線程),一直等到這個 queue 中排在它前面的任務都執行完成后才會開始執行自己,自己執行完畢后,再會取消阻塞,使這個 queue 中排在它后面的任務繼續執行。如果你傳入的是其他的 queue, 那么它就和 dispatch_async 一樣了。 例子代碼如下:

-(void)testGCDBarrierAsync{
NSLog(@"begin ---%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("testGCD.BarrierAsync", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"dispatch_async1");
});
dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:5];
    NSLog(@"dispatch_async2");
});
dispatch_barrier_async(queue, ^{
    NSLog(@"dispatch_barrier_async");
    [NSThread sleepForTimeInterval:5];
    
});
dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:1];
    NSLog(@"dispatch_async3");   
});
}

打印結果:

2016-03-09 14:42:14.239 testGCD[7320:2534975] begin ---{number = 1, name = main}
2016-03-09 14:42:16.244 testGCD[7320:2535015] dispatch_async1
2016-03-09 14:42:19.241 testGCD[7320:2535016] dispatch_async2
2016-03-09 14:42:19.241 testGCD[7320:2535016] dispatch_barrier_async
2016-03-09 14:42:25.247 testGCD[7320:2535016] dispatch_async3

請注意執行的時間,可以看到dispatch_barrier_async是在前面的任務執行結束后它才執行,而且它后面的任務等它執行完成之后才會執行。

dispatch_barrier_sync(queue, ^{ });這個方法的使用和上一個一樣,傳入 自定義的并發隊列(DISPATCH_QUEUE_CONCURRENT),它和上一個方法一樣的阻塞 queue,不同的是 這個方法還會 阻塞當前線程。如果你傳入的是其他的 queue, 那么它就和 dispatch_sync 一樣了。例子如下:

-(void)testGCDBarrierSync{
NSLog(@"begin ---%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("testGCD.BarrierSync", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"dispatch_Sync1");
});
dispatch_sync(queue, ^{
    [NSThread sleepForTimeInterval:5];
    NSLog(@"dispatch_Sync2");
});
dispatch_barrier_sync(queue, ^{
    NSLog(@"dispatch_barrier_Sync");
    [NSThread sleepForTimeInterval:5];
    
});
dispatch_sync(queue, ^{
    [NSThread sleepForTimeInterval:3];
    NSLog(@"dispatch_Sync3");
});
}

打印結果:

2016-03-09 14:58:10.146 testGCD[7449:2557946] begin ---{number = 1, name = main}
2016-03-09 14:58:12.151 testGCD[7449:2557946] dispatch_Sync1
2016-03-09 14:58:17.153 testGCD[7449:2557946] dispatch_Sync2
2016-03-09 14:58:17.154 testGCD[7449:2557946] dispatch_barrier_Sync
2016-03-09 14:58:25.157 testGCD[7449:2557946] dispatch_Sync3

從這個打印結果看不出什么,囧。。。但是此時如果UI上有什么交互的話,就不行了。自己可以另行實驗。

dispatch_apply(times, queue, ^(size_t index) { }); 表現得就像一個 for 循環,但它能并發地執行不同的迭代。這個函數是同步的,所以和普通的 for 循環一樣,它只會在所有工作都完成后才會返回。 簡單例子如下:

    -(void)testGCDForApply{
        dispatch_queue_t queue =   dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        dispatch_apply(5, queue, ^(size_t index) {
        // 執行5次
        NSLog(@"testGCDForApply");
      });
    }

打印出來的結果如下:

2016-03-09 15:15:01.340 testGCD[7593:2586389] testGCDForApply
2016-03-09 15:15:01.340 testGCD[7593:2586391] testGCDForApply
2016-03-09 15:15:01.340 testGCD[7593:2584865] testGCDForApply
2016-03-09 15:15:01.340 testGCD[7593:2586252] testGCDForApply
2016-03-09 15:15:01.340 testGCD[7593:2586390] testGCDForApply

其他用法#####

延遲執行:所謂延遲執行就是延時一段時間再執行某段代碼。下面是一些常用方法:
1.[NSThread sleepForTimeInterval:3];
雖然這種方法能起到延遲執行的作用,但別用,因為會卡住當前線程。
2.[self performSelector:@selector(download:) withObject:@"http://abc.jpg" afterDelay:5];
5秒后自動調用download:方法,并且傳遞參數。
3.NSTimer: 也能延遲執行。
[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(download:) userInfo:@"http://abc.jpg" repeats:NO];
4.GCD中的dispatch_after

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);        

double delay = 3;        

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), queue, ^{        
NSLog(@"------download------%@", [NSThread currentThread]);      
});

線程安全:為了防止多個線程搶奪同一個資源造成的數據安全問題。線程安全的代碼能在多線程或并發任務中被安全的調用,而不會導致任何問題(數據損壞,崩潰,等)。線程不安全的代碼在某個時刻只能在一個上下文中運行。也有兩種實現方法:

  1. 互斥鎖:給需要同步的代碼塊加一個互斥鎖,就可以保證每次只有一個線程訪問此代碼塊。
//()小括號里面放的是鎖對象 
@synchronized(self) {   
 //code here
}

2.同步執行:把多個線程需要執行的代碼添加到同一個串行隊列中,由于串行隊列一次只能執行一個任務,所以也能實現線程同步的作用。
- (void)saleTicket2
{
while (1) {
dispatch_sync(ticketQueue, ^{
NSInteger count = self.leftTicketCount;
if (count > 0) {

            [NSThread sleepForTimeInterval:0.05];
            
            self.leftTicketCount = self.leftTicketCount = count - 1;;
            NSLog(@"線程名:%@,售出1,剩余票數是:%ld,",[[NSThread currentThread] name],(long)self.leftTicketCount);
        }else{
            return ;
        }
    });
  }
}

GCD還有一些其他用法,比如創建單例:

單例模式:關于單例,有三件事是你必須要記住的:

  1. 單例必須是唯一的,所以它才被稱為單例。在一個應用程序的生命周期里,有且只有一個實例存在。單例的存在給我們提供了一個唯一的全局狀態。比如我們熟悉的NSNotification,UIApplication和NSUserDefaults都是單例。

  2. 為了保持一個單例的唯一性,單例的構造器必須是私有的。這防止其他對象也能創建出單例類的實例。

  3. 為了確保單例在應用程序的整個生命周期是唯一的,它就必須是線程安全的。簡單來說,如果你寫單例的方式是錯誤的,就有可能會有兩個線程嘗試在同一時間初始化同一個單例,這樣你就有潛在的風險得到兩個不同的單例。這就意味著我們需要用GCD的dispatch_once來確保初始化單例的代碼在運行時只執行一次。

     @interface Login : NSObject <NSCopying>
    
     +(instancetype)sharedLogin;
    
     @end
    

    @implementation Login

    static id _instance;
    

    +(instancetype)sharedLogin {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    _instance = [[Login alloc] init];
    });

        return _instance;
     }
    

    @end
    關于GCD目前就想到這些,寫到這。

NSOperation 和 NSOperationQueue#

NSOperation##

iOS并發編程中,把每個并發任務定義為一個Operation,對應的類名是NSOperation。對應GCD中的任務。NSOperation是一個抽象類,無法直接使用,它只定義了Operation的一些基本方法。我們需要創建一個繼承于它的子類或者使用系統預定義的子類。目前系統預定義了兩個子類:NSInvocationOperationNSBlockOperation。創建一個Operation后需要調用start來啟動任務,它會默認在當前隊列中同步執行

  • NSInvocationOperation
    NSInvocationOperation 是一個基于對象和selector的operation,使用這個只需要指定對象以及任務的selector,還可以設定傳遞的參數對象。

       // 創建操作
      NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(download) object:nil];
      // 開始執行,operation直接調用start,是同步執行(在當前線程執行操作)
      [operation start];
    

同時當這個operation執行完成后,還可以獲取operation中Invocation執行后返回的結果對象:

    id result = [operation result];
  • NSBlockOperation

運行一個Operation
在一個Block中執行一個任務,這時我們就需要用到NSBlockOperation。可以通過blockOperationWithBlock:方法來方便地創建一個NSBlockOperation:

  //創建NSBlockOperation對象
  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"---download---%@", [NSThread currentThread]);
  }];
   //開始執行任務
  [operation start];

start方法用來啟動一個Operation任務,這個operation默認會在當前線程中執行。

NSBlockOperation還有一個方法:addExecutionBlock,通過這個方法可以給operation添加多個執行block。這樣 Operation 中的任務會 并發執行,它會在主線程和其它的多個線程執行這些任務:

    //創建NSBlockOperation對象
  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"operation---%@", [NSThread currentThread]);
  }];

  //添加多個Block
  for (NSInteger i = 0; i < 6; i++) {
      [operation addExecutionBlock:^{
          NSLog(@"---download%ld:%@", i, [NSThread currentThread]);
      }];
  }

  //開始執行任務
  [operation start];

注意這打印結果:

2016-03-10 12:13:17.269 TestOperation[9900:3589128] ---download1:<NSThread: 0x7fc2405029e0>{number = 5, name = (null)}
2016-03-10 12:13:17.269 TestOperation[9900:3589136] ---download4:<NSThread: 0x7fc2418b7810>{number = 7, name = (null)}
2016-03-10 12:13:17.269 TestOperation[9900:3589091] operation---<NSThread: 0x7fc2404057a0>{number = 1, name = main}
2016-03-10 12:13:17.269 TestOperation[9900:3589129] ---download0:<NSThread: 0x7fc241a00040>{number = 2, name = (null)}
2016-03-10 12:13:17.269 TestOperation[9900:3589137] ---download3:<NSThread: 0x7fc240508e80>{number = 4, name = (null)}
2016-03-10 12:13:17.269 TestOperation[9900:3589135] ---download5:<NSThread: 0x7fc240606a80>{number = 6, name = (null)}
2016-03-10 12:13:17.269 TestOperation[9900:3589132] ---download2:<NSThread: 0x7fc24060f3b0>{number = 3, name = (null)}

取消Operation
要取消一個Operation,要向Operation對象發送cancel消息:
[operation cancel];
當向一個Operation對象發送cancel消息后,并不保證這個Operation對象一定能立刻取消,這取決于你的main中對cancel的處理。如果你在main方法中沒有對cancel進行任何處理的話,發送cancel消息是沒有任何效果的。為了讓Operation響應cancel消息,那么你就要在main方法中一些適當的地方手動的判斷isCancelled屬性,如果返回YES的話,應釋放相關資源并立刻停止繼續執行。

  • 自定義Operation
    自定義Operation 需要繼承 NSOperation,并實現Operation提供的main方法,你的所有任務都應該在main中進行處理。默認的start方法中會先做出一些異常判斷然后直接調用main方法。如果需要自定義一個NSOperation必須重載main方法來執行你所想要執行的任務。
 @implementation CustomOperation 

  -(void)main { 
     @try { 
        // Do some work. 
     } 
     @catch(...) { 
        // Exception handle. 
     } 
  } 
  @end 

NSOperationQueue##

NSOperationQueue是一個Operation執行隊列,你可以將任何你想要執行的Operation添加到Operation Queue中,以在隊列中執行。Operation Queue一共有兩種類型:主隊列其他隊列,只要添加到隊列中,會自動調用start()方法執行任務,無需再手動添加。

  • 主隊列
    創建主隊列:
    // 主隊列
    NSOperationQueue *queue = [NSOperationQueue mainQueue];

  • 其他隊列
    其他隊列的任務會在其他線程中并行執行。

    //創建NSBlockOperation對象
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operation---%@", [NSThread currentThread]);
    }];
    
    //添加多個Block
    for (NSInteger i = 0; i < 6; i++) {
      [operation addExecutionBlock:^{
          NSLog(@"---download%ld:%@", i, [NSThread currentThread]);
      }];
    }
    
    //開始任務,用NSOperationQueue無需手動start方法
    // [operation start];
    
     // 創建隊列
     NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 添加任務到隊列中(自動異步執行)
    [queue addOperation:operation];
    

打印結果:

2016-03-10 14:06:03.208 TestOperation[10144:3718109] ---download1:<NSThread: 0x7fc54ac0b510>{number = 8, name = (null)}
2016-03-10 14:06:03.208 TestOperation[10144:3718203] ---download0:<NSThread: 0x7fc54c000210>{number = 7, name = (null)}
2016-03-10 14:06:03.208 TestOperation[10144:3718107] operation---<NSThread: 0x7fc54ad11680>{number = 2, name = (null)}
2016-03-10 14:06:03.208 TestOperation[10144:3718240] ---download4:<NSThread: 0x7fc54c002110>{number = 5, name = (null)}
2016-03-10 14:06:03.208 TestOperation[10144:3718239] ---download3:<NSThread: 0x7fc54c300940>{number = 4, name = (null)}
2016-03-10 14:06:03.208 TestOperation[10144:3718241] ---download5:<NSThread: 0x7fc54c301730>{number = 6, name = (null)}
2016-03-10 14:06:03.208 TestOperation[10144:3718204] ---download2:<NSThread: 0x7fc54c2019b0>{number = 3, name = (null)}

NSOperationQueue還有一個添加任務的方法addOperationWithBlock

 NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
    // 異步下載圖片
    NSURL *url = [NSURL URLWithString:@"http://d.hiphotos.baidu.com/image/pic/item/37d3d539b6003af3290eaf5d362ac65c1038b652.jpg"];
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:data];
    // 回到主線程,顯示圖片
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        self.imageView.image = image;
    }];
}];

最大并發Operation數目
在一個Operation Queue中是可以同時執行多個Operation的,Operation Queue會動態的創建多個線程來完成相應Operation。具體的線程數是由Operation Queue來優化配置的,這一般取決與系統CPU的性能,比如CPU的核心數,和CPU的負載。但我們還是可以設置一個最大并發數的,那么Operation Queue就不會創建超過最大并發數量的線程。當設置最大并發數位1時,隊列中每次只能執行一個任務,這就是一個串行執行隊列了。

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1; 

Operation的依賴關系
有時候我們對任務的執行順序有要求,一個任務必須在另一個任務執行之前完成,這就需要用到Operation的依賴(Dependency)屬性。我們可以為每個Operation設定一些依賴的另外一些Operation,那么如果依賴的Operation沒有全部執行完畢,這個Operation就不會被執行。比如有個操作C依賴操作B,操作b又依賴操作A:

 // 1.創建一個隊列(非主隊列)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

// 2.創建3個操作
NSBlockOperation *operationC = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"---操作C---%@", [NSThread currentThread]);
}];

NSBlockOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"---操作A---%@", [NSThread currentThread]);
}];
NSBlockOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"---操作B---%@", [NSThread currentThread]);
}];

// 設置依賴
[operationB addDependency:operationA];
[operationC addDependency:operationB];

// 3.添加操作到隊列中(自動異步執行任務)
[queue addOperation:operationA];
[queue addOperation:operationB];
[queue addOperation:operationC];

打印輸出:

2016-03-10 14:52:01.518 TestOperation[10812:3782938] ---操作A---<NSThread: 0x7fc0e0d0b6b0>{number = 2, name = (null)}
2016-03-10 14:52:01.519 TestOperation[10812:3782945] ---操作B---<NSThread: 0x7fc0e2802a90>{number = 3, name = (null)}
2016-03-10 14:52:01.519 TestOperation[10812:3782945] ---操作C---<NSThread: 0x7fc0e2802a90>{number = 3, name = (null)}

如果將這些operation和它所依賴的operation加入某個隊列中,那么這些operation只有在它所依賴的operation都執行完畢后才可以被執行。注意:

  • 不能相互添加依賴,比如operationA依賴operationB,operationB 又依賴operationA,這會造成死鎖。
  • 可以在不同隊列之間添加依賴。
  • 可以使用removeDependency解除依賴。

Operation在隊列中執行的優先級
Operation在隊列中默認是按FIFO(First In First Out)順序執行的。同時我們可以為單個的Operation設置一個執行的優先級queuePriority,打亂這個順序。當Queue有空閑資源執行新的Operation時,會優先執行當前隊列中優先級最高的待執行Operation。
我從網上copy了一小段代碼來展示一下隊列優先級效果:

NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"------operation1 begin------");
    sleep(5);
    NSLog(@"------operation1 end------");
}];
operation1.queuePriority = NSOperationQueuePriorityHigh;

NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"------operation2 begin------");
    sleep(1);
    NSLog(@"------operation2 end------");
}];

NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"------operation3 begin------");
    sleep(2);
    NSLog(@"------operation3 end------");
}];

operation2.completionBlock = ^{
    NSLog(@"------operation2 finished in completionBlock------");
};

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 1;
[queue addOperation:operation2];
[queue addOperation:operation3];
[queue addOperation:operation1];
[queue waitUntilAllOperationsAreFinished];

設置最大并發數為1,以便任務能一個一個的執行。打印輸出:

2016-03-10 15:26:11.790 TestOperation[11068:3829921] -----test-----<NSThread: 0x7fa049501e00>{number = 1, name = main}
2016-03-10 15:26:11.791 TestOperation[11068:3829995] ------operation2 begin------
2016-03-10 15:26:12.864 TestOperation[11068:3829995] ------operation2 end------
2016-03-10 15:26:12.865 TestOperation[11068:3830132] ------operation2 finished in completionBlock------
2016-03-10 15:26:12.865 TestOperation[11068:3829996] ------operation1 begin------
2016-03-10 15:26:17.934 TestOperation[11068:3829996] ------operation1 end------
2016-03-10 15:26:17.934 TestOperation[11068:3830132] ------operation3 begin------
2016-03-10 15:26:20.006 TestOperation[11068:3830132] ------operation3 end------

從這個輸出結果可以看出,operation2執行完畢后會執行operation1,而不是operation3,這是因為operation1的優先級是NSOperationQueuePriorityHigh。這里還要注意點一就是,第一個加入到Operation Queue中的Operation,無論它的優先級有多么低,總是會第一個執行

設置Operation的completionBlock
每個Operation都可以設置一個completionBlock,在Operation執行完成時自動執行這個Block。我們可以在此進行一些完成的處理。completionBlock實現原理是對Operation的isFinnshed字段進行KVO(Key-Value Observing),當監聽到isFinnished變成YES時,就執行completionBlock。上面的小段代碼可以看出operation2的block執行完畢后立刻執行completionBlock

其他方法####

設置Operation的線程優先級:我們可以為Operation設置一個線程優先級,即threadPriority。那么執行main的時候,線程優先級就會調整到所設置的線程優先級。這個默認值是0.5,我們可以在Operation執行前修改它。
operation.threadPriority = 0.1;
注意:如果你重載的start方法,那么你需要自己來配置main執行時的線程優先級和threadPriority字段保持一致。

  • NSOperation

    //判斷任務是否正在執行
    BOOL executing;
    
    //判斷任務是否完成
    BOOL finished;
    
    //取消任務
    -(void)cancel;
    
    //阻塞當前線程直到此任務執行結束
    -(void)waitUntilFinished
    
    //是否異步執行任務
     BOOL asynchronous 
    
  • NSOperationQueue

    //獲取隊列任務數
    NSUInteger operationCount
    
    //暫停或是取消任務
    BOOL suspended;
    
    //取消所有任務
    - (void)cancelAllOperations;
    
    //阻塞當前線程直到隊列中的所有任務執行結束
    - (void)waitUntilAllOperationsAreFinished;
    

在最后再看看有哪些方式可以從其他線程回到主線程:

NSThead#####
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];
GCD#####
dispatch_async(dispatch_get_main_queue(), ^{

});
NSOperationQueue#####
[[NSOperationQueue mainQueue] addOperationWithBlock:^{

}];

關于多線程編程,還有其他很多功能,這里所寫的只是自己平時比較常用的。具體可以查看官方文檔,或是其他資料。本篇GCD的代碼例子可以到github上查看,NSOperation的代碼在這里

最后聲明:#

本文所記載的東西都是來自網上資料或是官方文檔,如有侵權,請及時告訴我,但所有代碼我都親自測試了一遍,知識水平有限,如果有錯可隨時指證,謝謝!!!希望站在巨人的肩膀上看得更遠一些。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 一、前言 上一篇文章iOS多線程淺匯-原理篇中整理了一些有關多線程的基本概念。本篇博文介紹的是iOS中常用的幾個多...
    nuclear閱讀 2,075評論 6 18
  • iOS開發中常用的幾種多線程方案,簡單做個小結,方便日后查閱。 NSThead GCD NSOperation &...
    木木小林醬閱讀 335評論 0 1
  • 在這篇文章中,我將為你整理一下 iOS 開發中幾種多線程方案,以及其使用方法和注意事項。當然也會給出幾種多線程的案...
    張戰威ican閱讀 616評論 0 0
  • 1. GCD簡介 什么是GCD呢?我們先來看看百度百科的解釋簡單了解下概念 引自百度百科:Grand Centra...
    千尋_544f閱讀 412評論 0 0
  • 意志力波動: 意志力的肌肉模式告訴我們意志力從早到晚是逐漸減弱的,或者說處于某一個狀態的時候意志力會比較強或者是弱...
    baiying閱讀 215評論 0 0