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