原文 Grand Central Dispatch Tutorial for Swift: Part 2/2html
原文做者:Bjrn Olav Ruudios
譯者:Ethan Joeswift
歡迎來到Grand Central Dispatch系列教程的第二部分!數組
在教程的第一部分,你學到了一些關於併發,線程及GCD工做原理的知識。你經過並用dispatch_barrier_async與dispatch_sync保證了PhotoManager單例在讀取與寫入照片過程當中的線性安全性。值得一提的是,你不只經過dispatch_after及時地向用戶發出提醒以此優化了App的UX並且還經過dispatch_async將部分工做從一個View Controller的實例化過程當中分割至另外一線程以此實現CPU高密度處理工做。xcode
假如你是一路從上一部分教程學過來的話,你徹底能夠在之前的工程文件上繼續Coding。但假如你沒有完成教程的第一部分或是不想繼續使用本身的工程文件的話,你可從這裏下載到教程第一部分的完整工程文件。安全
OK! 是時候探索一下更多關於GCD的知識了。服務器
修復提前出現的Popup網絡
也許你已經注意到了當你經過Le Internet的方式添加照片時,在全部照片下載完成前AlertView就已經跳出來提醒你「Download Complete」。閉包
See That?併發
其實問題出在PhotoManaer的downloadPhotosWithCompletion函數中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
var
storedError: NSError!
for
address
in
[OverlyAttachedGirlfriendURLString,
SuccessKidURLString,
LotsOfFacesURLString] {
let url = NSURL(string: address)
let photo = DownloadPhoto(url: url!) {
image, error
in
if
error != nil {
storedError = error
}
}
PhotoManager.sharedManager.addPhoto(photo)
}
if
let completion = completion {
completion(error: storedError)
}
}
|
在函數結尾部分調用completion閉包--這就表明着你認爲全部的照片的下載任務都已經完成。但不幸的是此時此刻你並沒有法保證全部的下載任務都已經完成。
DownloadPhoto類的實例化方法開始從一個URL下載文件並在下載完成前當即返回值。換句話說,downloadPhotosWithCompletion在函數結尾處調用其本身的completion閉包就好像它本身就是個有着直線型同步執行代碼的方法體,而且每一個方法執行完本身的工做後都會調用這個completed。
無論怎樣,DownloadPhoto(url:)是異步執行的而且當即返回--因此這個解決方案無論用。
再有,downloadPhotosWithCompletion應該在全部的照片下載任務都調用了completion閉包後再調用本身的completion閉包。那麼問題來了:你怎樣去監管那些同時執行的異步事件呢?你根本不會知道它們會什麼時候並以何種順序結束。
或許你能夠寫多個Bool值去跟蹤每一個任務的下載狀態。說實話,這樣作的話會感受有些low而且代碼看起來會很亂。
萬幸的是,派發組(dispatch groups)正是爲知足監管這種多異步completion的須要所設計的。
派發組(Dispatch Group)
當整組的任務都完成時派發組會提醒你。這些任務既能夠是異步的也能夠是同步的而且能夠在不一樣隊列中被監管。當全組任務完成時派發組能夠經過同步或異步的方式提醒你。只要有任務在不一樣隊列中被監管,dispatch_group_t實例便會在多個隊列中的持續監管這些不一樣的任務。
當組中的所有任務執行完畢後,GCD的API提供兩種方式向你發出提醒。
第一個即是dispatch_group_wait,這是一個在組內全部任務執行完畢前或在處理超時的狀況下限制當前線程運行的函數。在AlertView提前出現的狀況下,使用disptach_group_wait絕對是你的最佳解決方案。
打開PhotoManager.swift並用以下代碼替換原downloadPhotosWithCompletion函數:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
dispatch_async(GlobalUserInitiatedQueue) {
// 1
var
storedError: NSError!
var
downloadGroup = dispatch_group_create()
// 2
for
address
in
[OverlyAttachedGirlfriendURLString,
SuccessKidURLString,
LotsOfFacesURLString]
{
let url = NSURL(string: address)
dispatch_group_enter(downloadGroup)
// 3
let photo = DownloadPhoto(url: url!) {
image, error
in
if
let error = error {
storedError = error
}
dispatch_group_leave(downloadGroup)
// 4
}
PhotoManager.sharedManager.addPhoto(photo)
}
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER)
// 5
dispatch_async(GlobalMainQueue) {
// 6
if
let completion = completion {
// 7
completion(error: storedError)
}
}
}
}
|
代碼分步解釋:
一旦使用限制當前線程運行的同步式dispatch_group_wait,你必須用dispatch_async將整個方法調至後臺隊列以保證主線程的正常運行。
在這裏聲稱了一個你能夠將其視爲未完成任務計數器的新派發組。
dispatch_group_enter用來向派發組提醒新任務執行的開始。你必須使調用dispatch_group_enter的次數要相稱於調用dispatch_group_leave的次數,否則將會致使App崩潰。
在這裏,你向派發組提醒任務的執行結束。再強調一遍,進出派發組的次數必定要相等。
在全部任務執行結束後或者在處理超時的狀況下dispatch_group_wait纔會執行。假如在全部任務執行結束前就出現了處理超時的狀況,函數便會返回一個非零結果。你能夠將其放在一個特殊的閉包中以檢查是否會發生處理超時的狀況。固然,在本教程的狀況下你可使用DISPATCH_TIME_FOREVER令其保持等待請求狀態,這就意味它會一直等下去,由於照片的下載任務總會完成的。
到目前爲止,你保證了照片下載任務要麼順利完成要麼出現處理超時的狀況。其後你即可以返回至主隊列運行你的completion閉包。這將向主線程添加稍後將被執行的任務。
條件容許的狀況下執行completion閉包。
編譯並運行你的App,你會發如今點擊下載照片的選項後你的completion閉包將會在正確的時間執行。
提醒:當你在實體設備上運行App的時候,假如網絡機制運行過快以致於你沒法判斷的completion閉包開始執行時間的話,你能夠到App的Setting中的Developer Section中進行一些網絡調整。打開Network Link Conditioner,選擇Very Bad Network是一個不錯的選擇。
假如你在模擬器上運行App的話,你能夠經過使用Network Link Conditioner included in the Hardare IO Tools for Xcode調整你的網絡速度。這是一個當你須要瞭解在網絡情況很差的狀況下App執行狀況的絕佳工具。
這個解決方法的好處不止於此,但整體上來講它在大多數狀況下避免了限制線程正常運行的可能。你接下來的任務即是寫一個相同的並以異步的方式向你發出'照片下載完成'提醒的方法。
在開始以前先了解一下對於不一樣類型的隊列來講應該什麼時候使用並怎樣使用派發組的簡短教程。
自定義連續隊列(Custom Serial Queue): 在組內任務所有完成時須要發出提醒的狀況下,自定義連續隊列是一個不錯的選擇。
主隊列(Main Queue[Serial]):在當你以同步的方式等待全部任務的完成且你也不想限制主隊列的運行的狀況下你應該在主線程上警戒使用它。但好比像網絡請求這種長時間運行的任務結束時異步模型是用來更新UI的絕佳方式。
併發隊列(Concurrent Queue):這對於派發組及完成提醒也是個不錯的選擇。
派發組,再來一次!
出於精益求精的目的,經過異步的方式將下載任務派發到另外一個隊列並用dispatch_group_wait限制其運行的作法是否是有些stupid呢?試試另外一種方法吧...
用以下實現代碼代替PhtotManager.swift中的downloadPhotosWithCompletion函數:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
// 1
var
storedError: NSError!
var
downloadGroup = dispatch_group_create()
for
address
in
[OverlyAttachedGirlfriendURLString,
SuccessKidURLString,
LotsOfFacesURLString]
{
let url = NSURL(string: address)
dispatch_group_enter(downloadGroup)
let photo = DownloadPhoto(url: url!) {
image, error
in
if
let error = error {
storedError = error
}
dispatch_group_leave(downloadGroup)
}
PhotoManager.sharedManager.addPhoto(photo)
}
dispatch_group_notify(downloadGroup, GlobalMainQueue) {
// 2
if
let completion = completion {
completion(error: storedError)
}
}
}
|
這就是你的新異步方法的工做原理:
在這個新的實現方法中,當你再也不限制主線程的時候你就沒有必要將其放進dispatch_async的調用中。
dispatch_group_notify至關於一個異步completion閉包。當派發組中再也不剩餘任何任務且輪到completion閉包運行時,這段代碼將會執行。你也能夠定義你的completion代碼在哪一個隊列上運行。在這段代碼中你便要運行在主隊列上。
對於在不限制任何線程運行的狀況下處理這種特殊需求的例子來講,這是一種較爲簡潔的方式。
過多使用併發機制形成的危險
學了這麼多的新東西后,你是否是該令你的代碼所有實現線程化呢?
Do It !!!
看看你在PhotoManger中的downloadPhotosWithCompletion函數。你應該注意到了那個循環在三個參數間並下載三張不一樣照片的for循環。你接下來的工做即是嘗試經過併發機制加快for循環的運行速度。
該輪到dispatch_apply上場了。
dispatch_apply就像是一個以併發的形式執行不一樣迭代過程的for循環。就像是一個普通的for循環,dispatch_apply是一個同步運行且全部工做完成後纔會返回的函數。
當你在對閉包內已給定任務的數量進行最優化迭代過程數量的設定時必定要小心,由於這種存在多個迭代過程且每一個迭代過程僅包含少許工做的狀況所消耗的資源會抵消掉併發調用所產生的優化效果。這個叫作striding的技術會在你處理多任務的每一個迭代過程的地方幫到你。
何時適合用dispatch_apply呢?
自定義連續隊列(Custome Serial Queue):對於連續隊列來講,dispatch_apply沒什麼用處;你仍是老實地用普通的for循環吧。
主隊列(Main Queue[Serial]):跟上面狀況同樣,老實地用普通for循環。
併發隊列(Concurrent Queue):當你須要監管你的任務處理進程時,併發循環絕對是一個好主意。
回到downloadPhotosWithCompletion並替換成以下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
var
storedError: NSError!
var
downloadGroup = dispatch_group_create()
let addresses = [OverlyAttachedGirlfriendURLString,
SuccessKidURLString,
LotsOfFacesURLString]
dispatch_apply(addresses.count, GlobalUserInitiatedQueue) {
i
in
let index = Int(i)
let address = addresses[index]
let url = NSURL(string: address)
dispatch_group_enter(downloadGroup)
let photo = DownloadPhoto(url: url!) {
image, error
in
if
let error = error {
storedError = error
}
dispatch_group_leave(downloadGroup)
}
PhotoManager.sharedManager.addPhoto(photo)
}
dispatch_group_notify(downloadGroup, GlobalMainQueue) {
if
let completion = completion {
completion(error: storedError)
}
}
}
|
你的循環塊如今就是以併發的形式運行;在上述代碼中,你爲調用dispatch+apply提供了三個參數。第一參數聲明瞭迭代過程的數量,第二個參數聲明瞭將要運行多個任務的隊列,第三個參數聲明瞭閉包。
要知道盡管你已經有了在線程安全模式下添加照片的代碼,可是照片順序會根據最早完成的線程的順序所排列。
編譯並運行,經過Le Internet的方式添加一些照片。注意到有什麼不一樣嗎?
在真實設備上運行修改後的代碼偶爾會運行得快一些。因此,上面作出的修改值得嗎?
其實,在這種狀況下它不值得你這麼作。緣由以下:
你已經調用出了比for循環再同種狀況下消耗更多資源的線程。dispatch_apply在這裏顯得有些小題大作了。
你寫App的時間是有限的--不要爲那些'抓雞不成蝕把米'的優化代碼浪費時間,把你的時間用在優化得有明顯效果的代碼上。你能夠選擇使用Xcode中的Instruments來測試出你App中執行時間最長的方法。
在某些狀況下,優化後的代碼甚至會增長你和其餘開發者理解其邏輯結構的難度,因此優化效果必定要是物有所值的。
記住,不要癡迷於優化,要否則你就是和本身過不去了。
取消派發塊(dispatch block)的執行
要知道在iOS 8和OS X Yosemite中加入了名爲dispatch block objects的新功能(中文叫‘派發塊對象’感受老是怪怪的,因此就繼續用英文原名)。Dispatch block objects能夠作很多事兒了,好比經過爲每一個對象設定一個QoS等級來決定其在隊列中的優先級,但它最特別的功能即是取消block objects的執行。但你須要知道的是一個block object只有在到達隊列頂端且開始執行前才能被取消。
我們能夠經過‘利用Le Internet開始並再取消照片下載任務’的方式詳細描述取消Dispatch Block的運行機制。用下述代碼代替PhotoManager.swift中的downloadPhotosWithCompletion函數:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
var
storedError: NSError!
let downloadGroup = dispatch_group_create()
var
addresses = [OverlyAttachedGirlfriendURLString,
SuccessKidURLString,
LotsOfFacesURLString]
addresses += addresses + addresses
// 1
var
blocks: [dispatch_block_t] = []
// 2
for
i
in
0 ..< addresses.count {
dispatch_group_enter(downloadGroup)
let block = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS) {
// 3
let index = Int(i)
let address = addresses[index]
let url = NSURL(string: address)
let photo = DownloadPhoto(url: url!) {
image, error
in
if
let error = error {
storedError = error
}
dispatch_group_leave(downloadGroup)
}
PhotoManager.sharedManager.addPhoto(photo)
}
blocks.append(block)
dispatch_async(GlobalMainQueue, block)
// 4
}
for
block
in
blocks[3 ..< blocks.count] {
// 5
let cancel = arc4random_uniform(2)
// 6
if
cancel == 1 {
dispatch_block_cancel(block)
// 7
dispatch_group_leave(downloadGroup)
// 8
}
}
dispatch_group_notify(downloadGroup, GlobalMainQueue) {
if
let completion = completion {
completion(error: storedError)
}
}
}
|
addresses數組內包含每一個都被複制了三份的address變量。
這個數組包含着晚些時候將被使用的block objects。
dispatch_block_create聲明瞭一個新的block object。第一個參數是定義了不一樣block特性的標誌。這個標誌使得block繼承了其在被分配至隊列中的QoS等級。第二個參數是以一個閉包形式定義的block。
這是一個以異步形式分發到全局主隊列的block。在這個例子中所使用的主隊列是一個連續隊列,因此其更容易取消所選的blocks。定義了分發blocks的代碼已在主隊列上的執行保證了下載blocks的稍後執行。
除去前三次的下載,在剩餘的數組元素中執行for循環。
arc4random_uniform提供一個在0至上限範圍內(不包含上限)的整數。就像擲硬幣那樣,將2設定爲上限後你將會獲得0或1中的某一個整數。
假如在隨機數是一、block還在隊列中且沒有正在被執行的狀況下,block則被取消。在執行過程當中的block是不能被取消的。
當全部blocks都被加入分發隊列後,不要忘記刪除被取消的隊列。
編譯並運行App,經過Le Internet 的方式添加照片。你會發現App在下載了原來的三張照片後還會再下載一個隨機數量的照片。分配至隊列後,剩餘的blocks將被取消。儘管這是一個很牽強的例子但起碼它很好的描述了dispatch block objects如何被使用或被取消的。
Dispatch block objects能作的還有不少,使用前別忘了看看官方文檔。
GCD帶來的各類各樣的樂趣
且慢!再容我講點兒東西!其實這還有些不經常使用的函數,但在特殊狀況下它們是很是有用的。
測試異步代碼
這也許聽起來有些不靠譜,但你知道Xcode上的確有這項測試功能嗎?:]其實在某些狀況下我是僞裝不知道有這項功能的,可是在處理具備複雜關係的代碼的時候,代碼編寫與運行的測試是很是重要的。
Xcode中的測試功能是以XCTestCase的子類形式出現其且在其中運行的任何方法都是以test開頭出現的。測試功能在主線程上運行,因此你能夠假設每一個測試都是以一種連續(serial)的方式運行的。
只要一個給定的測試方法完成了執行,XCTest方法就會認定一個測試已經完成並開始下一個測試,這就意味着在新的測試運行的同時,上一個測試中的異步代碼還會繼續運行。
當你在執行一個網絡請求任務且不想限制主線程的運行時,那麼這類網絡任務一般是以異步方式執行的。這種「測試方法的結束表明着整個測試過程的結束」的機制加大了網絡代碼測試的難度。
別緊張,接下來我們看兩個經常使用的且專門用來測試以異步方式執行的代碼的技術:一個使用了信號量(semaphores),另外一個使用了指望(expectations)。
信號量(Semaphores)
在不少學校的OS課中,一提到大名鼎鼎的Edsger W.Dijkstra時確定會講到信號量這個跟線程相關的概念。信號量難懂之處在於它創建在那些複雜的操做系統的函數之上。
假如你想學習更多關於信號量的知識,請到這裏瞭解更多關於信號量理論的細節。假如你是個專一於學術研究的傢伙,從軟件開發的角度來看,關於信號量的經典例子確定就是哲學家進餐問題了。
信號量適用於讓你在資源有限的狀況下控制多個單位的資源消耗。舉個例子,假如你聲明瞭一個其中包含兩個資源的信號量,在同一時間內最多隻能有兩個線程訪問臨界區。其餘想使用資源的線程必須以FIFO(First Come, First Operate)的順序在隊列中等待。
打開GooglyPuffTests.swift並用以下代碼替換downloadImageURLWithString函數:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func downloadImageURLWithString(urlString: String) {
let url = NSURL(string: urlString)
let semaphore = dispatch_semaphore_create(0)
// 1
let photo = DownloadPhoto(url: url!) {
image, error
in
if
let error = error {
XCTFail(
"\(urlString) failed. \(error.localizedDescription)"
)
}
dispatch_semaphore_signal(semaphore)
// 2
}
let timeout = dispatch_time(DISPATCH_TIME_NOW, DefaultTimeoutLengthInNanoSeconds)
if
dispatch_semaphore_wait(semaphore, timeout) != 0 {
// 3
XCTFail(
"\(urlString) timed out"
)
}
}
|
如下即是信號量如何在上述代碼中工做的解釋:
建立信號量。參數代表了信號量的初始值。這個數字表明着能夠訪問信號量線程的數量,咱們常常以發送信號的方式來增長信號量。
你能夠在completion閉包中向信號量聲明你再也不須要資源了。這樣的話,信號量的值會獲得增長而且向其餘資源聲明此時信號量可用。
設定信號量請求超時的時間。在信號量聲明可用前,當前線程的運行將被限制。若出現超時的話,函數將會返回一個非零的值。在這種狀況下測試即是失敗的,由於它認爲網絡請求的返回不應使用超過十秒的時間。
經過使用菜單中的Product/Test選項或者使用?+U快捷鍵測試App的運行。
斷開網絡鏈接後再次運行測試;假如你在實機上運行就打開飛行模式,如果模擬器的話就斷開連接。10秒後此次測試便以失敗了結。
假如你是一個服務器團隊中一員,完成這些測試仍是挺重要的。
指望(Expectations)
XCTest框架提供了另外一個用來測試異步方式執行代碼的解決方案,指望。這便容許你在一個異步任務開始執行前設定一個指望--一些你期待發生的事。在異步任務的指望被標記爲已完成(fulfilled)前,你能夠令test runner一直保持等待狀態。
用如下代碼代替GooglyPufftests.swift中的downloadImageWithString函數:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
func downloadImageURLWithString(urlString: String) {
let url = NSURL(string: urlString)
let downloadExpectation = expectationWithDescription(
"Image downloaded from \(urlString)"
)
// 1
let photo = DownloadPhoto(url: url!) {
image, error
in
if
let error = error {
XCTFail(
"\(urlString) failed. \(error.localizedDescription)"
)
}
downloadExpectation.fulfill()
// 2
}
waitForExpectationsWithTimeout(10) {
// 3
error
in
if
let error = error {
XCTFail(error.localizedDescription)
}
}
}
|
解釋一下:
經過expectationWithDescription參數聲明瞭一個指望。當測試失敗的時候,test runner將會在Log中顯示這段字符串參數,以此表明着你所期待發生的事情。
調用以異步方式標記指望已完成的閉包中的fulfill.
調用的線程等待指望被waitForExpectationsWithTimeout函數標記完成。若等待超時,線程將被當作一個錯誤。
編譯並運行測試。儘管測試結果跟使用信號量機制比起來並無太多的不一樣,但這倒是一種使XCTest框架更加簡潔易讀的方法。
派發源(Dispatch Sources)的使用
GCD的一個很是有趣的特性就是派發源,它是包含了不少低層級別的功能。這些功能能夠幫你對Unix的信號,文件描述符,Mach端口、VFS Nodes進行反饋以及檢測。儘管這些東西超出了這篇教程的範圍,但我以爲你仍是要試着去實現一個派發源對象。
不少派發源的初學者常常被卡在如何使用一個源的的問題上,因此你要清楚dispatch_source_create的工做原理。下面的函數聲明瞭一個源:
1
2
3
4
5
|
func dispatch_source_create(
type: dispatch_source_type_t,
handle: UInt,
mask: UInt,
queue: dispatch_queue_t!) -> dispatch_source_t!
|
做爲第一個參數,type: dispatch_source_type_t決定了接下來的句炳以及掩碼參數的類型。你能夠去看一下相關內容的官方文檔以便理解每一種dispatch_source_type_t的用法與解釋。
在這裏你將監管DISPATCH_SOURCE_TYPE_SIGNAL。如官方文檔裏解釋的那樣:派發源監管着當前進程的信號,其句炳的值是一個整數(int),而掩碼值暫時沒有用到而被設爲0。
這些Unix信號能夠在名爲signal.h的頭文件中找到。文件的頂端有不少的#defines。在這堆信號中你將對SIGSTOP信號進行監管。當進程收到一個不可避免的暫停掛起指令時這個信號將被髮送。當你用LLDB的debugger除錯的時候一樣的信號也會被髮送。
打開PhotoCollectionViewController.swift文件,在viewDidLoad函數附近添加以下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#if DEBUG
private
var
signalSource: dispatch_source_t!
private
var
signalOnceToken = dispatch_once_t()
#endif
override func viewDidLoad() {
super
.viewDidLoad()
#if DEBUG // 1
dispatch_once(&signalOnceToken) {
// 2
let queue = dispatch_get_main_queue()
self.signalSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL,
UInt(SIGSTOP), 0, queue)
// 3
if
let source = self.signalSource {
// 4
dispatch_source_set_event_handler(source) {
// 5
NSLog(
"Hi, I am: \(self.description)"
)
}
dispatch_resume(source)
// 6
}
}
#endif
// The other stuff
}
|
分佈解釋以下:
從安全角度考慮,你最好在DEBUG模式下編譯代碼,以防其餘人間接地看到你的代碼。: ] 經過在如下路徑,Project Settings -> Build Settings -> Swift Compiler – Custom Flags -> Other Swift Flags -> Debug,經過在Debug選項中添加-D DEBUG的方式來定義DEBUG。
利用dispatch_once對派發源進行單次的初始化設定。
在這裏你實例化了一個signalSource變量並代表你只想進行信號監管並將SIGSTOP信號做爲第二個參數。再有一點你須要知道的是你使用主隊列處理接收到的事件--至於爲何,你待會兒就會知道了。
假如你提供了一個錯誤的參數,派發源對象將不會被建立成功。總之,在使用派發源對象前你要肯定你已經建立了一個可用的派發源對象。
dispatch_source_set_event_handler註冊了一個在收到特定任務信號時將被調用的事件處理閉包。
在默認的狀況下,全部派發源都是在暫停掛起的狀態下開始執行的。當你打算開始監管事件時,你必須讓派發源對象重新開始運行。
編譯並運行App;以debugger方式暫停App的運行並再馬上恢復運行。檢查一下控制檯,你會看到以下的反饋:
1
|
2014-08-12 12:24:00.514 GooglyPuff[24985:5481978] Hi, I am:
|
在某種程度上你的App如今知道debug了。這很是不錯,可是如何較爲實際地使用它呢?
當你重新恢復App運行時你可使用它對一個object進行debug並顯示其相關數據;當某些不懷好意的人想利用debugger影響App正常運行的時候,你也能夠爲你的App寫一個自定義安全登陸模塊。
另外一個有趣的想法即是經過上述機制實現一個對於debugger中對象的堆棧跟蹤器。
What ?
考慮一下這種狀況,你意外的中止了debugger的運行並在很大程度上debugger很難待在預計的棧幀上。但如今你能夠任什麼時候間中止debugger的運行並任何地方執行代碼。假如你想在App的特定位置中執行代碼,上述狀況將會很是有用。
在viewDidLoad的事件處理閉包中的NSLog代碼旁添加斷點。在debugger中暫停運行,在恢復運行;你的App將運行至斷點添加處。如今你即可以爲所欲爲地訪問PhotoCollectionViewController中的實例了。
假如你不知道debugger中有哪些線程,能夠去查看一下。主線程總會是第一個被libdispatch跟隨的線程;GCD的協調器總會是第二個線程;其餘的線程就要視具體狀況而定了。
利用斷點功能你能夠逐步地更新UI、測試類的屬性甚至在不從新運行App的狀況下執行特定的方法,看起來很方便吧!
Where to Go From Here?
你能夠在這裏下載到教程的完整代碼。
我挺不喜歡嘮叨的,可是我以爲你仍是應該去看看這篇關於如何使用Xcode中的Instruments的教程。假如你打算對你的App進行優化的話,你是絕對會用到Instruments的。要知道Instruments對於推斷相對執行的問題是頗有用處的:對比不一樣代碼塊中哪一塊的相對執行時間是最長的。
與此同時,你也有必要去看看這篇How to Use NSOperations and NSOperationsQueue Tutorial in Swift的教程。NSOperations能夠提供更良好的控制,實現多併發任務最大數量的處理以及在犧牲必定運行速度的狀況下使得程序更加面向對象化。
記住!在大多數狀況下,除非你有着特殊的緣由,必定要儘可能使用更高級別的API。只有當你真的想到學到或作到一些很是有趣的事情時再去探索蘋果的Dark Arts吧!
祝你好運!
本文轉載自:http://www.cocoachina.com/ios/20150626/12238.html