在前面的博客中如果用到了異步請求的話,也是用到的第三方的東西,沒有正兒八經的用過iOS中多線程的東西。其實多線程的東西還是蠻重要的,如果對於之前學過操作系統的小伙伴來說,理解多線程的東西還是比較容易的,今天就做一個小的demo來詳細的了解一下iOS中的多線程的東西。可能下面的東西會比較枯燥,但還是比較實用的。
多線程用的還是比較多的,廢話少說了,下面的兩張截圖是今天我們實驗的最終結果,應該是比較全的,小伙伴們由圖來分析具體的功能吧:
功能說明:
1、點擊同步請求圖片,觀察整個UI界面的變化,並點擊測試按鈕,紅色是否會變成綠色。
2、NSThread按鈕,是由NSThread方式創建線程並執行相應的操作。
3、Block操作按鈕是用Block創建操作,並在操作隊列中執行,下面的是Invocation操作
4、serial是GCD中的串行隊列,concurrent是GCD中的並行隊列
好啦,上面的鹹蛋先到這兒,代碼該走起啦。
一、准備階段
1.不管使用代碼寫,還是storyboard或者xib等,先把上面所需的控件初始化好以便使用
2.點擊測試UI按鈕,改變下邊label的顏色的代碼如下:
1 //改變lable的顏色,在紅綠顏色之間進行交換 2 - (IBAction)tapTestButton:(id)sender { 3 static int i = 1; 4 if (i == 1) { 5 _testLabel.backgroundColor = [UIColor redColor]; 6 i = 0; 7 } 8 else 9 { 10 _testLabel.backgroundColor = [UIColor greenColor]; 11 i = 1; 12 } 13 14 }
3.從網絡上獲取圖片,並使用主線程顯示進程調用情況
1 //從wang'lu獲取圖片數據 2 -(NSData *) getImageData 3 { 4 5 _count ++; 6 int count = _count; //線程開始啟動 7 NSString *str = [NSString stringWithFormat:@"%d.線程%@",count,[NSThread currentThread]]; NSLog(@"%@",str);
8 NSData *data; 9 [NSThread sleepForTimeInterval:0.5]; 10 data = [NSData dataWithContentsOfURL:[NSURL URLWithString:IMAGEURL]]; 11 12 NSString *str = [NSString stringWithFormat:@"%d.線程%@完畢",count,[NSThread currentThread]]; 13 //請求數據的任務由其他線程解決,所以LogTextView的內容由主線程更新,也只有主線程才能更新UI 14 [self performSelectorOnMainThread:@selector(updateTextViewWithString:) withObject:str waitUntilDone:YES]; 15 return data; 16 }
4.上面的用到了主線程來調用updateTextViewWithString方法,因為只有主線程才能更新UI,updateTextViewWithString:這個方法負責把線程的執行信息顯示在View上,代碼如下:
1 //在ViewController上顯示圖片請求情況 2 -(void)updateTextViewWithString:(NSString *)str 3 { 4 NSString *old_str = [NSString stringWithFormat:@"%@\n%@",_logTextView.text, str]; 5 6 _logTextView.text = old_str; 7 //改變Label的顏色,便於觀察 8 [self tapTestButton:nil]; 9 }
5.把請求完的圖片加載到ImageView上
1 //更新圖片 2 -(void) updateImageWithData:(NSData *)data 3 { 4 UIImage *image = [UIImage imageWithData:data]; 5 [_testImage setImage:image]; 6 }
6.加載圖片的,也就是請求數據後在ImageView上顯示
1 //由其他線程請求數據,由主線程來更新UI 2 -(void)loadImageWithThreadName:(NSString *)threadName 3 { 4 [[NSThread currentThread] setName:threadName]; 5 6 NSData *data = [self getImageData]; 7 [self performSelectorOnMainThread:@selector(updateImageWithData:) withObject:data waitUntilDone:YES]; 8 }
二、通過各種方式來
1.同步請求圖片測試,請求數據和更新UI都放在主線程中順序執行,這樣在請求數據的時候UI會卡死,代碼如下;
1 //同步請求圖片,視圖阻塞的,因為主線程被占用,無法進行視圖的更新 2 - (IBAction)tapButton:(id)sender { 3 NSData *data = [self getImageData]; 4 [self updateImageWithData:data]; 5 }
2.NSThread創建線程測試,用detachNewThreadSelector方法來創建新的線程會自動啟動並執行,而不用調用start方法。代碼如下:
1 //NSThread 2 - (IBAction)tapButton2:(id)sender { 3 //點擊一次button就創建一個新的線程來請求圖片數據 4 for (int i = 0;i < 10; i ++) { 5 [NSThread detachNewThreadSelector:@selector(loadImageWithThreadName:) toTarget:self withObject:@"NSThread"]; 6 } 7 }
3.NSInvocationOperation的使用,新建一個調用操作,然後添加到隊列中執行,代碼如下:
1 //NSInvocationOperation 2 - (IBAction)tapInvocationOperation:(id)sender { 3 4 5 //上面的調用操作需要放到調用隊列裡才執行的 6 //創建操作隊列 7 NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; 8 9 for (int i = 0;i < 10; i ++) { 10 //實例化一個調用操作,來執行數據請求 11 NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadImageWithThreadName:) object:@"Invocation"]; 12 //把上面的調用操作放到操作隊列裡,隊列會自動開啟一個線程調用我們指定的方法 13 [operationQueue addOperation:invocationOperation]; 14 } 15 }
4.block的操作,新建一個block操作,並添加到隊列中執行,代碼如下:
1 //BlockOperation 2 - (IBAction)tapBlockOperation:(id)sender { 3 __weak __block ViewController *copy_self = self; 4 5 //創建BlockOperation 6 NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ 7 [copy_self loadImageWithThreadName:@"Block"]; 8 }]; 9 10 //添加到操作隊列 11 NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; 12 [operationQueue addOperation:blockOperation]; 13 14 for (int i = 0;i < 10; i ++) { 15 16 //另一種方式 17 [operationQueue addOperationWithBlock:^{ 18 [copy_self loadImageWithThreadName:@"Block"]; 19 }]; 20 } 21 }
5.GCD中的串行隊列:
1 //串行隊列 2 - (IBAction)tapGCDserialQueue:(id)sender { 3 //創建串行隊列 4 dispatch_queue_t serialQueue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL); 5 6 7 __weak __block ViewController *copy_self = self; 8 9 10 for (int i = 0;i < 10; i ++) { 11 //異步執行隊列 12 dispatch_async(serialQueue, ^{ 13 [copy_self loadImageWithThreadName:@"Serial"]; 14 }); 15 } 16 17 18 }
6.GCD中的並行隊列:
1 //並行隊列 2 - (IBAction)tapGCDConcurrentQueue:(id)sender { 3 //創建並行隊列 4 dispatch_queue_t concurrentQueue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT); 5 __weak __block ViewController *copy_self = self; 6 7 for (int i = 0;i < 10; i ++) { 8 //異步執行隊列 9 dispatch_async(concurrentQueue, ^{ 10 [copy_self loadImageWithThreadName:@"Concurrent"]; 11 }); 12 13 } 14 15 }
以上是各個按鈕對應的方法,下面的截圖是執行結果:
三、線程間的同步問題(為我們的線程添加上同步鎖)
在操作系統中講多線程時有一個名詞叫髒數據,就是多個線程操作同一塊資源造成的,下面就修改一下代碼,讓數據出現問題,然後用同步鎖來解決這個問題
1.在getImageData方法(標題一中的第3個方法)中有兩條語句。這個用來顯示線程的標號。上面的標號是沒有重復的。
1 _count ++; 2 int count = _count;
在兩條語句中間加一個延遲,如下:
_count ++; [NSThread sleepForTimeInterval:1]; int count = _count;
如果運行的話,會有好多標號是重復的,如圖一,__count是成員變量,多個線程對此他進行操作,所以會出現標號不一致的情況,下面我們加上同步鎖
(1)用NSLock加同步鎖,代碼如下:
1 //通過NSLock加鎖 2 [_lock lock]; 3 _count ++; 4 [NSThread sleepForTimeInterval:1]; 5 int count = _count; 6 [_lock unlock];
(2)通過@synchronized加同步鎖,代碼如下:
1 //通過synchronized加鎖 2 int count; 3 @synchronized(self){ 4 _count ++; 5 [NSThread sleepForTimeInterval:1]; 6 count = _count; 7 }
加鎖前後的運行效果如下:
GCD的串行隊列開始執行的順序如下,下面是是在一個線程中按FIFO的順序執行的:
GCD中的並行隊列,是在不同的線程中同時執行的: