轉載自:http://blog.sina.com.cn/s/blog_6dce99b10101atsu.html,html
http://blog.csdn.net/q199109106q/article/details/8566300 ,尊重原創!編程
詳解IOS開發應用之併發Dispatch Queues是本文要介紹的內容,咱們幾乎能夠調度隊列去完成全部用線程來完成的任務。網絡
調度隊列相對於線程代碼更簡單,易於使用,更高效。下面講主要簡述調度隊列,在應用中如何使用調度隊列去執行任務。數據結構
一、關於調度隊列併發
全部的調度隊列都是先進先出隊列,所以,隊列中的任務的開始的順序和添加到隊列中的順序相同。GCD自動的爲咱們提供了一些調度隊列,咱們也能夠建立新的用於具體的目的。app
下面列出幾種可用的調度隊列類型以及如何使用。異步
(1)serial queues(串行隊列)又稱私有調度隊列(private),通常用在對特定資源的同步訪問上。咱們能夠根據須要建立任意數量的串行隊列,每個串行隊列之間是併發的。async
(2)並行隊列,又稱global dispatch queue。並行隊列雖然能夠併發的執行多個任務,可是任務開始執行的順序和其加入隊列的順序相同。咱們本身不能去建立並行調度隊列。只有三個可用的global concurrent queues。ide
(3)main dispatch queue 是一個全局可用的串行隊列,其在行用程序的主線程上執行任務。此隊列的任務和應用程序的主循環(run loop)要執行的事件源交替執行。由於其運行在應用程序的主線程,main queue常常用來做爲應用程序的一個同步點。異步編程
二、關於隊列的一些技術
除了調度隊列,GCD還提供了一些有用的技術來幫助咱們管理代碼。
三、使用blocks去實現tasks
block objects是基於C語言的特徵,能夠用在C,C++ Objective-c中。一個block雖然和函數指針有些類似,可是實際上表明一個底層數據結構,相似與對象,有編譯器去建立和管理。
block的一個優點是可使用其本身做用域外的變量,例如,一個block能夠讀取其父做用域的變量值,此值是copy到了block heap的數據結構中。當block被加入到dispatch queue中,這些值一般爲只讀形式。
block的聲明和函數指針相似,只是把*改成了^,咱們能夠傳遞參數給block,也能夠接收其返回的值。
四、建立和管理調度隊列
(1)得到全局併發調度隊列(global concurrent dispath queues)
系統給每個應用程序提供了三個concurrent dispatch queues。這三個併發調度隊列是全局的,它們只有優先級的不一樣。由於是全局的,咱們不須要去建立。咱們只須要經過使用函數dispath_get_global_queue去獲得隊列,以下:
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
除了獲得default的併發隊列,還能夠經過傳遞參數DISPATCH_QUEUE_PRIOPITY_HIGH和DISPATCH_QUEUE_PRIOPITY_LOW去獲得高優先級或者低優先級的。(第二個參數是爲之後擴展保留的)
雖然dispatch queue是引用計數對象,可是在此由於隊列是全局的,不須要咱們去retain或者release,咱們須要使用的時候直接調用函數dispath_get_global_queue就能夠。
(2)建立串行調度隊列
當想要任務按照某一個特定的順序執行時,串行隊列是頗有用的。串行隊列在同一個時間只執行一個任務。咱們可使用串行隊列代替鎖去保護共享的數據。和鎖不一樣,一個串行隊列能夠保證任務在一個可預知的順序下執行。
和併發隊列不一樣,咱們要本身去建立和管理串行隊列,能夠建立任意數量的串行隊列。當咱們建立串行隊列時,應出於某種目的,如保護資源,或者同步應用程序的某些關鍵行爲。
下面的代碼表述了怎麼建立一個自定義的串行隊列,函數dispath_queue_create須要兩個參數,隊列的名字,隊列的屬性。調試器和性能工具顯示隊列的名字幫助咱們去跟蹤任務是如何執行,隊列的屬性被保留供未來使用,應該爲NULL
dispatch_queue_t queue; queue = dispatch_queue_create("com.example.MyQueue", NULL);
除了本身建立的自定義隊列,系統會自動的給我建立一個串行隊列並和應用程序的主線程綁定到一塊兒。下面講述如何得到它。
(3)運行時得到常見的隊列
GCD提供了一些函數讓咱們可以方便的訪問到common dispatch queues
使用dispatch_get_current_queue函數用來調試或者測試得到當前隊列的標識。
使用函數dispatch_get_main_queue能夠獲得與應用程序主線程相連的串行調度隊列。
(4)調度隊列的內存管理
調度隊列是引用計數類型,當咱們建立串行調度隊列時,咱們要release它。能夠使用函數dispatch_retain和dispatch_release去增長或者減小引用計數。
(5)在一個隊列中存儲自定義context information
全部的調度對象容許咱們讓其與一個自定義上下文數據關聯,經過函數dispatch_set_context和dispatch_get_context來使用,系統不會去使用咱們的自定義數據,咱們本身在恰當的時間去分配和釋放。
對於隊列,上下文數據一般用來存儲一個指向對象的指針,或者其餘的數據結構,咱們能夠在隊列的finalizer函數中去釋放context data。下面將給一個例子。
(6)爲隊列提供一個clean up 函數。
當咱們建立串行調度隊列以後,咱們可讓其和一個finalizer函數相連用來清理隊列中須要清理的數據。咱們可使用dispatch_set_finalizer_f函數去設置一個函數,當隊列的引用計數爲0時會去自動的調用。使用此函數去清理和隊列相關聯的context data,當context 指針不會NULL時,此函數就會調用。
shows a custom finalizer function and a function that creates a queue and installs that finalizer. The queue uses the finalizer function to release the data stored in the queue’s context pointer. (The myInitializeDataContextFunction and myCleanUpDataContextFunction functions referenced from the code are custom functions that you would provide to initialize and clean up the contents of the data structure itself.) The context pointer passed to the finalizer function contains the data object associated with the queue. void myFinalizerFunction(void *context) { MyDataContext* theData = (MyDataContext*)context; // Clean up the contents of the structure myCleanUpDataContextFunction(theData); // Now release the structure itself. free(theData); } dispatch_queue_t createMyQueue() { MyDataContext* data = (MyDataContext*) malloc(sizeof(MyDataContext)); myInitializeDataContextFunction(data); // Create the queue and set the context data. dispatch_queue_t serialQueue = dispatch_queue_create("com.example.CriticalTaskQueue", NULL); if (serialQueue) { dispatch_set_context(serialQueue, data); dispatch_set_finalizer_f(serialQueue, &myFinalizerFunction); } return serialQueue; }
五、在隊列中添加一個任務
(1)有兩種方式在隊列中添加一個任務,同步或者異步。儘量使用dispatch_async和dispatch_async_f 函數去執行,比同步的要首選。當咱們向隊列中添加一個塊對象或者函數時,咱們沒有方法去知道此代碼什麼時間執行。
使用異步不會去阻塞主線程。
雖然儘量異步添加任務,在有些時候同步的方式去添加一個任務會防止一些同步錯誤。同步的方式調用函數dispatch_sync和dispatch_sync_f。此函數阻塞主線程的執行,直到指定的任務完成。
dispatch_async(serialQueue, ^{ [_friendList addObject:@"234"]; NSLog(@"testSerial, 111 count = %lu",(unsigned long)_friendList.count); });
(2)在任務完成的時候執行completion block
當任務完成時,咱們應用程序須要獲得通知,以便去處理結果,在傳統的異步編程中,咱們可能會使用回調函數,可是在調度隊列中,咱們使用completion block。
void average_async(int *data, size_t len,
dispatch_queue_t queue, void (^block)(int)) { // Retain the queue provided by the user to make sure it does not disappear before the completion block can be called. dispatch_retain(queue); // Do the work on the default concurrent queue and then call the user-provided block with the results.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ int avg = average(data, len); dispatch_async(queue, ^{ block(avg);}); // Release the user-provided queue when done dispatch_release(queue); }); }
(3)併發的執行循環迭代(loop iterations)
對於for循環,若是每一次的迭代相互都沒有影響,能夠併發的去執行迭代,使用函數dispatch_apply或者dispatch_apply_f 函數.
和正常的循環同樣,函數dispatch_apply或者dispatch_apply_f直到全部的循環迭代完成時才返回。
以下代碼:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(count, queue, ^(size_t i) { printf("%un",i); });
(4)在主線程上執行任務
咱們能夠經過調用函數dispatch_get_main_queue 去去獲得主線程的調度隊列。
(5)延遲執行任務
dispatch_after 異步
double delayInSeconds = 0.5; dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_after(time, globalParallelQueue, ^{ [_friendList addObject:@"234"]; NSLog(@"testParallel, 111 count = %lu",(unsigned long)_friendList.count); });
(6)循環執行任務
int i; int count = 10; for (i = 0; i < count; i++) { printf("%d ",i); }
若是每次迭代執行的任務與其它迭代獨立無關,並且循環迭代執行順序也可有可無的話,你能夠調用dispatch_apply或dispatch_apply_f函數來替換循環。
這兩個函數爲每次循環迭代將指定的block或函數提交到queue。當dispatch到併發 queue時,就有可能同時執行多個循環迭代。
用dispatch_apply或dispatch_apply_f時你能夠指定串行或併發 queue。併發queue容許同時執行多個循環迭代,而串行queue就沒太大必要使用了。
下面代碼使用dispatch_apply替換了for循環,你傳遞的block必須包含一個size_t類型的參數,用來標識當前循環迭代。第一次迭代這個參數值爲0,最後一次值爲count - 1。
// 得到全局併發queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
size_t count = 10; dispatch_apply(count, queue, ^(size_t i) { printf("%zd ", i); });
打印信息:
1 2 0 3 4 5 6 7 8 9
能夠看出,這些迭代是併發執行的
和普通for循環同樣,dispatch_apply和dispatch_apply_f函數也是在全部迭代完成以後纔會返回,所以這兩個函數會阻塞當前線程,主線程中調用這兩個函數必須當心,可能會阻止事件處理循環並沒有法響應用戶事件。
因此若是循環代碼須要必定的時間執行,能夠考慮在另外一個線程中調用這兩個函數。若是你傳遞的參數是串行queue,並且正是執行當前代碼的queue,就會產生死鎖。 同步。
(7)暫停、繼續隊列
咱們可使用dispatch_suspend函數暫停一個queue以阻止它執行block對象;使用dispatch_resume函數繼續dispatch queue。
調用dispatch_suspend會增長queue的引用計數,調用dispatch_resume則減小queue的引用計數。當引用計數大於0時,queue就保持掛起狀態。所以你必須對應地調用suspend和resume函數。
掛起和繼續是異步的,並且只在執行block之間(好比在執行一個新的block以前或以後)生效。掛起一個queue不會致使正在執行的block中止。
(8)dispath_group的使用
假設有這樣一個需求:從網絡上下載兩張不一樣的圖片,而後顯示到不一樣的UIImageView上去,通常能夠這樣實現
// 根據url獲取UIImage - (UIImage *)imageWithURLString:(NSString *)urlString { NSURL *url = [NSURL URLWithString:urlString]; NSData *data = [NSData dataWithContentsOfURL:url]; return [UIImage imageWithData:data]; } - (void)downloadImages { // 異步下載圖片 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 下載第一張圖片 NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg"; UIImage *image1 = [self imageWithURLString:url1]; // 下載第二張圖片 NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg"; UIImage *image2 = [self imageWithURLString:url2]; // 回到主線程顯示圖片 dispatch_async(dispatch_get_main_queue(), ^{ self.imageView1.image = image1; self.imageView2.image = image2; }); }); }
雖然這種方案能夠解決問題,但其實兩張圖片的下載過程並不須要按順序執行,併發執行它們能夠提升執行速度。
有個注意點就是必須等兩張圖片都下載完畢後才能回到主線程顯示圖片。Dispatch Group可以在這種狀況下幫咱們提高性能。
下面先看看Dispatch Group的用處:
咱們能夠使用dispatch_group_async函數將多個任務關聯到一個Dispatch Group和相應的queue中,group會併發地同時執行這些任務。並且Dispatch Group能夠用來阻塞一個線程, 直到group關聯的全部的任務完成執行。
有時候你必須等待任務完成的結果,而後才能繼續後面的處理。
下面用Dispatch Group優化上面的代碼:
// 根據url獲取UIImage
- (UIImage *)imageWithURLString:(NSString *)urlString {
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [NSData dataWithContentsOfURL:url];
// 這裏並無自動釋放UIImage對象
return [[UIImage alloc] initWithData:data];
}
- (void)downloadImages {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 異步下載圖片
dispatch_async(queue, ^{
// 建立一個組
dispatch_group_t group = dispatch_group_create();
__block UIImage *image1 = nil;
__block UIImage *image2 = nil;
// 關聯一個任務到group
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 下載第一張圖片
NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";
image1 = [self imageWithURLString:url1];
});
// 關聯一個任務到group
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 下載第一張圖片
NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";
image2 = [self imageWithURLString:url2];
});
// 等待組中的任務執行完畢,回到主線程執行block回調
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
self.imageView1.image = image1;
self.imageView2.image = image2;
// 千萬不要在異步線程中自動釋放UIImage,由於當異步線程結束,異步線程的自動釋放池也會被銷燬,那麼UIImage也會被銷燬
// 在這裏釋放圖片資源
[image1 release];
[image2 release];
});
// 釋放group
dispatch_release(group);
});
}
dispatch_group_notify函數用來指定一個額外的block,該block將在group中全部任務完成後執行。
小結:詳解IOS開發應用之併發Dispatch Queues的內容介紹完了,但願經過本文的學習能對你有所幫助!
---恢復內容結束---