IOS異步獲取數據並刷新界面dispatch_async的使用方法
在ios的開發和學習中多線程編程是必須會遇到並用到的。
在java中以及Android開發中,大量的後台運行,異步消息隊列,基本都是運用了多線程來實現。
同樣在,在ios移動開發和Android基本是很類似的一種模型。
但是很多時候,在應用開發中,我們會發現本身並沒有自己編碼去處理一些並發的事件,去開辟新的子線程等等。
(雖然一般的調用sdk發起一個網絡請求,系統都是會默認給你新起一個線程去處理的)。
整個程序看上去基本就是在Main線程中執行。
確實也是這樣的一種現象,因為我們基本都是在操作控件的布局,對控件數據添加,對於UI對象的更新都是在主線程的進行。
即便等下我們看到我們開啟了一個新的子線程用來獲取處理數據,最後還是需要通過通知UI主線程來刷新。
當然了,ios本身也是和大部分語言一樣,有NSThread線程類(我們都知道java中我們用到這個類)。
這些系統比較底層的api類,可以被我用來書寫自己的並發線程和操作隊列。
學過Android的我們都知道Handler,Looper這個概念,Looper說白了就是一個主線程的消息循環隊列,handler一般理解就是用於子線程和UI主線程一些數據交互。
看了下ios的GCD特性,發現他們之間頗有幾分相似。
1.下面來看下如何使用gcd編程的異步
Objective-c代碼
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- // 處理耗時操作的代碼塊...
- //通知主線程刷新
- dispatch_async(dispatch_get_main_queue(), ^{
- //回調或者說是通知主線程刷新,
- });
- });
復制代碼
dispatch_async開啟一個異步操作,第一個參數是指定一個gcd隊列,第二個參數是分配一個處理事物的程序塊到該隊列。
dispatch_get_global_queue(0, 0),指用了全局隊列。
一般來說系統本身會有3個隊列。
global_queue,current_queue,以及main_queue.
獲取一個全局隊列是接受兩個參數,第一個是我分配的事物處理程序塊隊列優先級。分高低和默認,0為默認2為高,-2為低
Objective-c代碼
- #define DISPATCH_QUEUE_PRIORITY_HIGH 2
- #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
- #define DISPATCH_QUEUE_PRIORITY_LOW (-2)
復制代碼
處理完事物後,需要將結果返回給或者是刷新UI主線程,同樣,和上面一樣,抓取主線程,程序塊操作。
//天啊,手賤不小心點到了home間,會退後發現沒保存~~~寫的並發一塊內容都沒了!!!
二:GCD之並發概念
其實對於編程中,我們一直提及到的幾個概念,同步,異步,並發,鎖等。
有時覺得一下子還真說不清。
下面我們以上面提到的圖片加載來看下這3個概念我的理解
1同步:
Objective-c代碼
- for (int i = 0 ; i < 10; i++) {
-
- UIImage *img = [self getImgeWith:[urlArr objectForIndex:i]];
- [myImgV[i] setImage:img];
-
- }
復制代碼
假設我要加載10個圖片,我現在擁有這些圖片的資源地址,保存在一個數組中。
我們先以獲取第一張圖片來舉例:
同步執行的概念就是,我獲取完第一張圖片的,
執行了for循環第一句返回了img後,我才能執行第二句,UI界面的刷新。
如果第一句返回的時間需要10秒,那我程序的響應就仿佛一直卡在這裡一樣,我無法進行其他操作。必須等它返回!!
因此,同步的一個很好理解的感念就是,一步走到黑。
2.異步
- for (int i = 0 ; i < 10; i++) {
- dispatch_async(dispatch_get_global_queue(0, 0), ^{
- // 處理耗時操作的代碼塊...
- UIImage *img = [self getImgeWith:[urlArr objectForIndex:i]];
- //通知主線程刷新
- dispatch_async(dispatch_get_main_queue(), ^{
- //回調或者說是通知主線程刷新,
- [myImgV[i] setImage:img];
- });
-
- });
復制代碼
看了這代碼,我們會說,異步操作那個假設還是要10秒啊,總體看來,執行一張圖片的時間加載還是要在10秒左右啊,
貌似異步沒什麼鳥用麼。但是,別忽略了其中一點,也黑絲核心的一點,此時我們圖片獲取操作放在裡一個線程隊列裡,
此刻,雖然我們看著圖片的加載還是需要10秒才會出來,但是,在這10秒期間,我們的UI主線程是可以操作的,比如界面上有個按鈕,你是可以按的
而不是如上面的同步,在10面期間,我是只能干等著,什麼都做不了。
異步的核心概念就是一個新線程,一個消息回調通知。
3.並行
我們還是以上代碼為例。前面我強調了,我們只看一張圖片的加載,現在,回到我們第一眼看到代碼的思維上去,
一個for循環。其實上面代碼過後,我是創建了10個異步線程。
好吧,到此,我們應該明白這三個概念了。
同步,其實我前面的例子舉得有些局限,就是這個例子本身就說明不需要同步執行,然後給大家大感覺是
同步是編程中一個忌諱點一樣,其實不然,很多時候。我們真是需要同步來做一些限制(比如線程中提出的同步鎖?聽著就感覺有用麼
雖然可能並不如我們想的那樣的運用同步,但是至少說明這個概念同樣是有用的)
我還是以剛才那個加載圖片為例子,來個簡單的說明如何運用同步的好處。
當然,我只是模擬一個同步的情況。
假設我們現在圖片的加載是這樣的,圖片本身為在加載前是一個默認的圖片,上面寫著,點擊我加載,點擊後會調用網絡加載方法,然後圖片顯示加載中,
然後我們雙擊圖片時(當然,理論上是在加載完後)讀取圖片網絡圖片放大,好吧,到這裡應該能想到要表達的情況了。
整個流程應該是點擊圖片->加載->雙擊查看。那如果成了點擊->加載中(以返回了圖片的作者和信息)-》雙擊圖片(通過前面請求返回的大圖鏈接顯示大圖)-》
完全加載返回(返回了大圖鏈接)。此時我們看不到圖像的大圖了。因為我們操作在返回前了,也就是說,
很多時候,我們下一個動作的操作必須需要用到前面一個操作的數據時,我們會給他做認為的同步編程,比如加個按鈕鎖。
這是我們又會疑惑道,下一個執行需要用到前一個執行的,那第一個例子中的for循環的第二句不是要用到麼,這麼說
他們必須要同步啊,如果你這麼想了,好巧,我們想到一塊去了~
但是,注意,前面我們到的異步是為了解決我點擊其他按鈕的操作,而不是說更新UI操作。下載和更新UI操作在我們看來必須是同步的
這是對的,但是那種做導致了系統本身一些監聽事件監聽到點擊處理在那個請求之後了,這邊的加載圖片其實要看成一次事件執行,
因為對於事件的這一抽象單元,其實是一種可人為定義的寬廣度。
也就是說,一次數據獲取和圖像填充,其實算是一個圖像獲取加載事件,事件可以說包含兩個單元,加載和填充。
而整個這個事件對於我們點擊其他按鈕並無關系,那麼也就說明了無需同步。
有道理啊,但是若果我們要點擊這個圖片呢,也就是回到剛才那個可以雙擊的假設。
此處也許我麼又忽略了一點為什麼加載中我們能點擊雙擊呢,也就這樣的假設是獲取圖片已經做了異步,但是我們下一步操作又是需要同步的
因此做了人為的同步鎖定。
好了,說的太多了,當時至少我們明白兩點
異步可能是為了反正耗時操作造成的主線程堵塞,
同步是為了解決一些不必要錯誤和麻煩。也許到這裡,我們腦中會聯想到的所謂的線程安全性。
其實同步以及同步鎖,卻是應該是考慮到這樣的不必要和不安全因素。
最後在簡單闡述下異步和並發關系。
其實看了上面說的,異步只是提供了一種多線程處理的概念,
並發是更像是異步的一種大規模實現。
就好比說,異步提出了可以用小弟去收保護費,收完了告訴並交給自己,而我在期間做其他要做的事。
並發突然想到,異步這個很有道理啊,那我有4個地方要收,一個小弟去收,雖然我還是可以閒著做其他的事,
但是小弟跑四個地方,我拿到錢所需要的時間還是和我自己去收一樣的,只不過我不用那麼費勁了,還能做其他事了。
因此,並發覺得應該派四個小弟去,因為每個場地的保護費各不相干的。(剛看了個紐約黑幫~)。
因此說,異步解決了線程堵塞,而並發則是在異步的基礎上,提高了符合特性事件的處理時間效率。
當然,如果10個圖片本身相互間是沒什麼聯系,但是,最後一個事件需要處理計算這10個圖片的總容量值。
那麼可以用 dispatch_group_async。
具體就看文檔吧。
總體來說,看了iosGCD這塊,一是讓我熟悉了block編程特性,還有是熟悉如何使用ios提供的GCD特性
來完成多線程編程。
常用的方法dispatch_async
為了避免界面在處理耗時的操作時卡死,比如讀取網絡數據,IO,數據庫讀寫等,我們會在另外一個線程中處理這些操作,然後通知主線程更新界面。
用GCD實現這個流程的操作比前面介紹的NSThread NSOperation的方法都要簡單。代碼框架結構如下:
[cpp] view plaincopy
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- // 耗時的操作
- dispatch_async(dispatch_get_main_queue(), ^{
- // 更新界面
- });
- });
如果這樣還不清晰的話,那我們還是用上兩篇博客中的下載圖片為例子,代碼如下:
[cpp] view plaincopy
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- NSURL * url = [NSURL URLWithString:@"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"];
- NSData * data = [[NSData alloc]initWithContentsOfURL:url];
- UIImage *image = [[UIImage alloc]initWithData:data];
- if (data != nil) {
- dispatch_async(dispatch_get_main_queue(), ^{
- self.imageView.image = image;
- });
- }
- });