麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁(yè) > 學(xué)院 > 開(kāi)發(fā)設(shè)計(jì) > 正文

在Swift中應(yīng)用GrandCentralDispatch(下)

2019-11-14 19:30:57
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

img-3-e1412704300353.jpg

本文由loveltyoic(博客翻譯自raywenderlich,原文:Grand Central Dispatch Tutorial for Swift: Part 1/2

歡迎來(lái)到本GCD教程的第二同時(shí)也是最終部分!

在第一部分中,你學(xué)到了并發(fā),線程以及GCD的工作原理。通過(guò)使用dispatch_barrrier和dispatch_sync,你做到了讓PhotoManager單例在讀寫(xiě)照片時(shí)是線程安全的。除此之外,你用到dispatch_after來(lái)提示用戶,優(yōu)化了用戶體驗(yàn)。還有,使用dispatch_async異步執(zhí)行CPU密集型任務(wù),從而為視圖控制器初始化過(guò)程減負(fù)。

如果你跟著教程做,現(xiàn)在可以從第一部分的示例工程繼續(xù)。如果你沒(méi)有完成第一部分或不想再用你的工程,可以下載第一部分的完成文件

是時(shí)候進(jìn)一步探索GCD了!

糾正過(guò)早出現(xiàn)的彈窗

你可能注意到,當(dāng)你通過(guò) Le Internet 選項(xiàng)添加照片時(shí),會(huì)有提示框在圖片下載完成之前就彈出,如下圖:

27.jpg

錯(cuò)誤在于 PhotoManager 里的 downloadPhotosWithCompletion:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)
  }
}

這里在方法的最后調(diào)用completion閉包——你會(huì)想當(dāng)然的認(rèn)為所有圖片都下載完了。但不幸的是,在此時(shí)無(wú)法保證。

DownloadPhoto類的實(shí)例方法從一個(gè)URL下載圖片并且不等下載完成就立即退出。換言之,downloadPhotosWithCompletion在最后調(diào)用completion閉包,就好像其中的所有方法都在順序執(zhí)行,并且在每個(gè)方法完成后才執(zhí)行下一個(gè)。

然而,DownloadPhoto(url:)是異步并且立即返回的——所以目前的方式不能正常工作。

downloadPhotosWithCompletion應(yīng)該在所有圖片下載任務(wù)都完成后再調(diào)用自己的completion閉包。問(wèn)題是:你怎么監(jiān)視并發(fā)的異步事件呢?你不知道它們何時(shí)完成,以何種順序。

也許你可以用多個(gè)Bool值來(lái)追蹤下載情況,但那不容易擴(kuò)展。而且坦白講,那是很丑陋的代碼。

幸運(yùn)的是,dispatch groups就是專為監(jiān)視多個(gè)異步任務(wù)的完成情況而設(shè)計(jì)的。

調(diào)度組(Dispatch Groups)

調(diào)度組在一組任務(wù)都完成后會(huì)發(fā)出通知。這些任務(wù)可以是異步或同步的,甚至可以分布在不同的隊(duì)列。調(diào)度組還可以通過(guò)同步或異步的方式來(lái)通知。因?yàn)槿蝿?wù)在不同的隊(duì)列中,disptch_group_t實(shí)例用來(lái)追蹤隊(duì)列中的不同任務(wù)。

在組內(nèi)所有事件都完成時(shí),GCD API提供了兩種方式發(fā)送通知。

第一種是dispatch_group_wait,它會(huì)阻塞當(dāng)前進(jìn)程,直到所有任務(wù)都完成或是等待超時(shí)。這正是我們的例子中需要的方式。

打開(kāi) 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
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)
      }
    }
  }
}

逐一來(lái)看注釋:

  • 因?yàn)槭褂胐ispatch_group_wait阻塞了當(dāng)前進(jìn)程,要用dispatch_async將整個(gè)方法放到后臺(tái)隊(duì)列,才能保證主線程不被阻塞。

  • 創(chuàng)建一個(gè)調(diào)度組,作用好比未完成任務(wù)的計(jì)數(shù)器

  • dispatch_group_enter通知調(diào)度組一個(gè)任務(wù)已經(jīng)開(kāi)始。你必須保證dispatch_group_enter和dispatch_group_leave是成對(duì)調(diào)用的,否則程序會(huì)崩潰。

  • 通知任務(wù)已經(jīng)完成。再一次,這里保持進(jìn)和出相匹配。

  • dispatch_group_wait等待所有任務(wù)都完成直到超時(shí)。如果在任務(wù)完成前就超時(shí)了,函數(shù)會(huì)返回一個(gè)非零值。可以通過(guò)返回值來(lái)判斷是否等待超時(shí);不過(guò),這里你用DISPATCH_TIME_FOREVER來(lái)表示一直等待。這意味著,它會(huì)永遠(yuǎn)等待!沒(méi)關(guān)系,因?yàn)閳D片總是會(huì)下載完的。

  • 此時(shí),你可以保證所有圖片任務(wù)都完成或是超時(shí)了。接下來(lái)在主隊(duì)列中加入完成閉包。閉包晚些時(shí)候會(huì)在主線程中執(zhí)行。

  • 執(zhí)行閉包。

運(yùn)行app,下載幾張圖片,留意你的app是如何表現(xiàn)的。

Note:如果網(wǎng)速太快以至于分辨不出何時(shí)執(zhí)行的閉包,你可以修改設(shè)備的設(shè)置。在 Setting 中的Developer Section 。打開(kāi) Network Link Conditioner,選擇“Very Bad Network”。

如果在模擬器上,用工具變更網(wǎng)速。這是你武器庫(kù)中一個(gè)很好的工具,它讓你清楚在不佳的網(wǎng)絡(luò)下你的app會(huì)發(fā)生什么。

這個(gè)方案目前不錯(cuò),但最好能避免阻塞進(jìn)程。你下一步的工作是重寫(xiě)這個(gè)方法來(lái)異步通知下載完成。

在學(xué)習(xí)下一個(gè)調(diào)度組的用法前,先看看怎樣在不同的隊(duì)列類型下使用調(diào)度組。

  • 自定義順序隊(duì)列:好選擇。當(dāng)一組任務(wù)完成時(shí)用它發(fā)送通知。

  • 主隊(duì)列(順序):在當(dāng)前情景下是不錯(cuò)的選擇。但你要謹(jǐn)慎地在主隊(duì)列中使用,因?yàn)橥降却腥蝿?wù)會(huì)阻塞主線程。然而,當(dāng)一個(gè)需要較長(zhǎng)時(shí)間的任務(wù)(比如網(wǎng)絡(luò)請(qǐng)求)完成時(shí),異步更新UI是很好的選擇。

  • 并發(fā)隊(duì)列:好選擇。用于調(diào)度組和通知。

調(diào)度組,再來(lái)一次

做的不錯(cuò),但是異步調(diào)度到另一個(gè)隊(duì)列然后用 dispatch_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?) {
  // 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)
    }
  }
}

異步方法是如何工作的:

  • 新的實(shí)現(xiàn)不需要把方法放進(jìn)dispatch_async中,因?yàn)槟悴](méi)有阻塞主線程。

  • dispatch_group_notify異步執(zhí)行閉包。當(dāng)調(diào)度組內(nèi)沒(méi)有剩余任務(wù)的時(shí)候閉包才執(zhí)行。同樣要指明在哪個(gè)隊(duì)列中執(zhí)行閉包。當(dāng)下,你需要在主隊(duì)列中執(zhí)行閉包。

這是更優(yōu)雅的方法,并且不會(huì)阻塞任何進(jìn)程。

并發(fā)過(guò)多帶來(lái)的危險(xiǎn)

通過(guò)支配這些新工具,你應(yīng)該將每件事都線程化,對(duì)嗎?

Thread_All_The_Code_Meme.jpg

看看PhotoManager中的downloadPhotosWithCompletion。你會(huì)發(fā)現(xiàn)通過(guò)for循環(huán)下載了三張圖片。現(xiàn)在來(lái)看看能否通過(guò)并發(fā)執(zhí)行for循環(huán)來(lái)提速。

是時(shí)候請(qǐng)出dispatch_apply了。

dispatch_apply像for循環(huán)一樣,只不過(guò)它會(huì)并發(fā)地執(zhí)行循環(huán)過(guò)程。這個(gè)函數(shù)是同步的,所以像普通的for循環(huán)一樣,dispatch_apply在所有工作都完成后才返回。

要注意循環(huán)的最佳次數(shù),如果有太多循環(huán)但每個(gè)循環(huán)內(nèi)只有很小的工作量,那么額外的開(kāi)銷(xiāo)會(huì)抹殺掉并發(fā)帶來(lái)的好處。 步進(jìn) (striding)可以幫助到你。它讓你在每次循環(huán)中做多件工作。

什么時(shí)候用dispatch_apply合適?

  • 自定義順序隊(duì)列:在順序隊(duì)列中使用dispatch_apply完全無(wú)意義;它的效果和for循環(huán)一樣。

  • 主隊(duì)列(順序):理由同上,用for循環(huán)就可以了。

  • 并發(fā)隊(duì)列:明智之選,尤其是你需要追蹤任務(wù)進(jìn)度時(shí)。

替換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
func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
  var storedError: NSError!
  var downloadGroup = dispatch_group_create()
  let addresses = [OverlyAttachedGirlfriendURLString,
                   SuccessKidURLString,
                   LotsOfFacesURLString]
  
  dispatch_apply(UInt(addresses.count), GlobalUserInitiatedQueue) {
    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)
    }
  }
}

現(xiàn)在你的循環(huán)可以并發(fā)執(zhí)行了;調(diào)用 dispatch_apply 時(shí),第一個(gè)參數(shù)是循環(huán)的次數(shù),第二個(gè)參數(shù)是執(zhí)行任務(wù)的隊(duì)列,第三個(gè)參數(shù)是閉包。

盡管你的代碼在添加圖片時(shí)是線程安全的,但是圖片的順序取決于線程完成的順序。

運(yùn)行app,用 Le Internet 添加一些圖片,發(fā)現(xiàn)不同了嗎?

在真機(jī)上運(yùn)行新的代碼會(huì)發(fā)現(xiàn) 些許 的速度提升。但是這值得嗎?

實(shí)際上,在這里并不值得這么做。原因如下:

  • 你很可能因?yàn)椴⑿卸ㄙM(fèi)了比f(wàn)or循環(huán)更多的開(kāi)銷(xiāo)。你應(yīng)該結(jié)合合適的步長(zhǎng)對(duì) 非常大 的集合使用dispatch_apply。

  • 開(kāi)發(fā)app的時(shí)間有限——不要花時(shí)間過(guò)早優(yōu)化。如果你想優(yōu)化,那么就優(yōu)化那些值得優(yōu)化的東西。用Instruments測(cè)試app以找到最耗時(shí)間的方法。如何使用Instruments。

  • 一般說(shuō)來(lái),代碼優(yōu)化會(huì)讓你的代碼變得更復(fù)雜。你要確定帶來(lái)的好處值得你增加復(fù)雜性。

記住,不要癡迷于優(yōu)化。否則只會(huì)讓你自己為難,也讓看你代碼的人抓狂。

取消調(diào)度塊

iOS 8 和 OS X Yosemite引入了 調(diào)度對(duì)象塊 (dispatch block object)。它們實(shí)現(xiàn)起來(lái)就像對(duì)閉包再包裝一層。調(diào)度對(duì)象塊可以做到很多事情,比如為隊(duì)列中的對(duì)象設(shè)置QoS等級(jí)來(lái)決定優(yōu)先級(jí),但最顯著的能力是可以取消塊的執(zhí)行。要明白對(duì)象塊只有在輪到它執(zhí)行之前才可以取消(一旦開(kāi)始執(zhí)行就不能取消了)。

為了說(shuō)明這個(gè)問(wèn)題,首先用 Le Internet 下載一些圖片,然后取消它們。替換 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
40
41
42
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 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)
    }
  }
}
  • 擴(kuò)展addresses數(shù)組,將每個(gè)地址復(fù)制3份。

  • 這個(gè)數(shù)組用來(lái)保存接下來(lái)創(chuàng)建的對(duì)象塊。

  • dispatch_block_create創(chuàng)建一個(gè)對(duì)象塊。第一個(gè)參數(shù)是一個(gè)表明了塊特征的標(biāo)志。此處的標(biāo)志讓塊從它進(jìn)入的隊(duì)列那里繼承QoS等級(jí)。第二個(gè)參數(shù)是閉包形式的塊定義。

  • 塊被異步的調(diào)度到全局主隊(duì)列。這里用全局主隊(duì)列是因?yàn)樗且粋€(gè)順序隊(duì)列,可以方便我們?nèi)∠麑?duì)象塊。當(dāng)前代碼已經(jīng)在主線程中執(zhí)行著,所以你可以保證下載任務(wù)將在此之后才執(zhí)行(也就是這個(gè)downloadPhotosWithCompletion返回后才輪到下載任務(wù)執(zhí)行)。

  • 取數(shù)組中第三個(gè)到結(jié)尾的部分。

  • arc4random_uniform會(huì)隨機(jī)返回一個(gè)0到上界之間(不含上界)的整數(shù)。以2為上界會(huì)得到0或1,像投硬幣一樣。

  • 如果隨機(jī)數(shù)是1,則取消塊。前提是,塊還在隊(duì)列中并且沒(méi)開(kāi)始。塊在執(zhí)行的過(guò)程中是不可以取消的。

  • 因?yàn)樗袎K都加入調(diào)度組了,不要忘記移除被取消的那些塊。

運(yùn)行,從 Le Internet 添加圖片。你會(huì)看到app下載3張圖片,以及隨機(jī)數(shù)量的額外圖片。那些沒(méi)下載的圖片是因?yàn)樵诩尤腙?duì)列 后 被取消了。這是一個(gè)刻意設(shè)計(jì)的例子,但是很好的演示了怎樣使用調(diào)度對(duì)象塊以及如何取消它。

調(diào)度對(duì)象塊能做更多事情,別忘了查看文檔。

五花八門(mén)的GCD趣用

等等!還有更多!下面展示一些常規(guī)用途之外的功能。盡管你不會(huì)經(jīng)常使用這些工具,但他們可能在特定情況下非常有用。

測(cè)試異步代碼

這聽(tīng)起來(lái)很瘋狂,但是你知道Xcode擁有測(cè)試功能嗎?:]我知道,有時(shí)我喜歡假裝它不存在,但是編寫(xiě)和運(yùn)行測(cè)試對(duì)構(gòu)建復(fù)雜的代碼很重要。

Xcode中的測(cè)試運(yùn)行在XCTestCase的子類之下,它會(huì)運(yùn)行所有以test開(kāi)頭的方法。測(cè)試跑在主線程下,所以你可以認(rèn)為測(cè)試是順序執(zhí)行的。

一旦給定的測(cè)試方法返回了,XCTest 會(huì)認(rèn)為這個(gè)測(cè)試完成了而去做下一個(gè)測(cè)試。這就是說(shuō),在下一個(gè)測(cè)試執(zhí)行過(guò)程中,前一個(gè)測(cè)試中的異步代碼也在繼續(xù)執(zhí)行。

網(wǎng)路請(qǐng)求通常是異步的,因?yàn)槟悴幌胱枞骶€程。一旦測(cè)試方法返回,測(cè)試也就結(jié)束了,因此很難對(duì)網(wǎng)絡(luò)請(qǐng)求做測(cè)試。

我們簡(jiǎn)單看一下兩種普遍的測(cè)試異步代碼的方法:信號(hào)量(semaphores)和 期望(expectations)。

信號(hào)量

信號(hào)量是一個(gè)古老學(xué)院派的線程概念,它是由謙遜的Edsger W. Dijkstra提出的。信號(hào)量是很復(fù)雜的話題,因?yàn)樗⒃阱e(cuò)綜復(fù)雜的操作系統(tǒng)函數(shù)之上。

如果你想了解更多信號(hào)量的知識(shí),查閱細(xì)說(shuō)信號(hào)量原理。如果你是學(xué)院派,有一個(gè)用到了信號(hào)量的經(jīng)典軟件開(kāi)發(fā)問(wèn)題叫做哲學(xué)家進(jìn)餐問(wèn)題

信號(hào)量讓你控制多個(gè)消費(fèi)者對(duì)有限資源的獲取。例如,如果你創(chuàng)建一個(gè)信號(hào)量來(lái)控制擁有2個(gè)資源的資源池,那么同一時(shí)刻最多有兩個(gè)線程可以進(jìn)入臨界區(qū)。其它也想使用資源的線程必須在FIFO隊(duì)列中等待。

打開(kāi) GooglyPuffTests.swift 并替換掉 downloadImageURLWithString:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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")
  }
}

以上代碼中信號(hào)量的工作原理: 
1. 創(chuàng)建信號(hào)量。參數(shù)表明信號(hào)量起始值。這個(gè)值代表了起始階段可以獲取信號(hào)量的線程數(shù)目(增加信號(hào)量就是發(fā)信號(hào),用0做初始值代表當(dāng)前沒(méi)有線程可以獲取信號(hào)量)。 2. 在完成閉包中,你告訴信號(hào)量不再需要資源。這會(huì)使信號(hào)量增加,同時(shí)給其他等待資源的任務(wù)發(fā)信號(hào),通知當(dāng)前信號(hào)量可用。 
3. 等待信號(hào)量并設(shè)置超時(shí)時(shí)間。這個(gè)調(diào)用會(huì)阻塞當(dāng)前進(jìn)程直到收到信號(hào)。非0返回表示等待已超時(shí)。在這種情況下,測(cè)試失敗,因?yàn)榫W(wǎng)絡(luò)請(qǐng)求不應(yīng)該超過(guò)10秒——相當(dāng)合理的假設(shè)! 
(譯者注:說(shuō)下我的理解:首先創(chuàng)建了信號(hào)量,但此時(shí)因?yàn)樾盘?hào)量是0,沒(méi)有線程可以獲取它,注釋3中對(duì)信號(hào)量的等待會(huì)阻塞。只有在圖片下載好了以后,才會(huì)發(fā)送一個(gè)信號(hào)量,那么注釋3對(duì)信號(hào)量的獲取就成功了,并退出等待。但如果圖片下載失敗呢?就不會(huì)調(diào)用注釋2這句觸發(fā)信號(hào)的語(yǔ)句,那么注釋3就會(huì)等待超時(shí),從而測(cè)試失敗。)

PRoduct/Test 或 cmd+U 運(yùn)行測(cè)試。測(cè)試應(yīng)該成功。

斷掉網(wǎng)絡(luò)連接并再次測(cè)試;如果在真機(jī)測(cè)試,請(qǐng)開(kāi)啟飛行模式。如果在模擬器上,直接斷網(wǎng)就好了。測(cè)試在10秒后會(huì)返回失敗的結(jié)果。很好,起作用了!

這是相當(dāng)微不足道的測(cè)試,但是如果你和服務(wù)端團(tuán)隊(duì)一起工作,這些基礎(chǔ)測(cè)試可以避免一些涉及網(wǎng)絡(luò)問(wèn)題的無(wú)端指責(zé)。

期望(expectations)

XCTest框架提供了另一種使用 期望 來(lái)測(cè)試異步代碼的方法。這種特性讓你首先設(shè)置你的期望——你希望發(fā)生的事——然后再開(kāi)始異步任務(wù)。接下來(lái)測(cè)試會(huì)一直等待,直到異步任務(wù)將期望標(biāo)記為 已完成 。

替換 GooglyPuffTests.swift 中的downloadImageURLWithString:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)
    }
  }
}

工作原理: 
1. 用expectationWithDescription生成期望。測(cè)試會(huì)在日志上顯示其中的字符串參數(shù),所以請(qǐng)描述你期望發(fā)生的事。 
2. 在異步執(zhí)行的閉包中調(diào)用fulfill來(lái)標(biāo)記期望已達(dá)成。 
3. 調(diào)用線程用waitForExpectationsWithTimeout等待期望達(dá)成。如果等待超時(shí)會(huì)視為出錯(cuò)。

運(yùn)行測(cè)試。結(jié)果和使用信號(hào)量沒(méi)什么不同,但使用XCTest框架是更清晰易讀的方案。

調(diào)度源(Dispatch Sources)

GCD中存在一個(gè)特別有趣的特性叫調(diào)度源,它是一個(gè)包含底層功能的百寶囊,幫助你響應(yīng)或監(jiān)控Unix信號(hào),文件描述符(file descriptors),Mach端口,VFS Nodes,以及其他復(fù)雜的東西。所有這些都超出了本教程的范圍,但是你可以嘗試著使用一下調(diào)度源對(duì)象。

第一次使用調(diào)度源的用戶可能會(huì)迷失其中,所以你首先要理解dispatch_source_create的工作原理。下面是創(chuàng)建它的函數(shù)原型:

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!

第一個(gè)參數(shù)type: dispatch_source_type_t是最重要的參數(shù),因?yàn)樗枋隽司浔╤andle)和掩碼(mask)參數(shù)。你需要查看Xcode文檔來(lái)弄清楚dispatch_source_type_t的參數(shù)有哪些可選項(xiàng)。

這里你會(huì)監(jiān)視DISPATCH_SOURCE_TYPE_SIGNAL。如文檔所述:

調(diào)度源監(jiān)控當(dāng)前進(jìn)程的信號(hào)。句柄(handle)是信號(hào)數(shù)字(int)。掩碼(mask)沒(méi)用到(傳0)。

Unix信號(hào)列表可以從signal.h找到。在頂部有一串#define。在這些信號(hào)列表中,你將要監(jiān)控SIGSTOP信號(hào)。這個(gè)信號(hào)會(huì)在進(jìn)程接收到不可抗拒的掛起指令時(shí)被發(fā)送。這個(gè)信號(hào)與你用LLDB debugger調(diào)試程序時(shí)發(fā)送的信號(hào)相同。

進(jìn)入 PhotoCollectionViewController.swift ,在viewDidLoad附近添加下面的代碼。你需要為類添加兩個(gè)私有屬性,并在viewDidLoad的開(kāi)始處添加段代碼,在調(diào)用superclass和ALAssetLibrary之間:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#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
}

這段代碼有點(diǎn)難懂,因此逐個(gè)注釋來(lái)講解: 
1. 最好只在DEBUG模式下編譯這段代碼,因?yàn)檫@可能讓不懷好意者洞見(jiàn)很多信息。:] 在 Project Settings –> Build Settings –> Swift Compiler – Custom Flags –> Other Swift Flags –> Debug 下添加 -D DEBUG 。 
2. 用dispatch_once一次性初始化調(diào)度源。 
3. 初始化signalSource變量。你指明對(duì)信號(hào)感興趣并且提供SIGSTOP做第二個(gè)參數(shù)。除此之外,你用主隊(duì)列處理接收到的事件——稍后你會(huì)發(fā)現(xiàn)為什么。 
4. 如果參數(shù)錯(cuò)誤,調(diào)度源對(duì)象不會(huì)被創(chuàng)建。因此,你應(yīng)該在使用它之前確保調(diào)度源是有效的。 
5. dispatch_source_set_event_handler注冊(cè)了一個(gè)事件處理閉包,當(dāng)你接收到監(jiān)控的信號(hào)時(shí)會(huì)調(diào)用這個(gè)閉包。 
6. 默認(rèn)情況下,所有調(diào)度源在開(kāi)始都處于掛起狀態(tài)。當(dāng)你想監(jiān)視事件時(shí),必須讓源對(duì)象繼續(xù)執(zhí)行。

運(yùn)行app;暫停調(diào)試器然后立即恢復(fù)。檢查控制臺(tái)(console),你會(huì)看到類似下面的信息:

1
2014-08-12 12:24:00.514 GooglyPuff[24985:5481978] Hi, I am:

你的app現(xiàn)在可以感知到調(diào)試(debugging-aware)了!這真棒,但在現(xiàn)實(shí)中怎樣用它呢?

你可以用它調(diào)試一個(gè)對(duì)象并在恢復(fù)app時(shí)展示數(shù)據(jù);你也可以自定義一些安全邏輯來(lái)保護(hù)app,當(dāng)惡意攻擊者在你的程序上附著調(diào)試器的時(shí)候。

有趣的想法是把這個(gè)方法當(dāng)做堆棧追蹤工具,來(lái)找到你想要在調(diào)試器中修改的對(duì)象。

16.jpg

設(shè)想一下這樣的場(chǎng)景。當(dāng)你意外地停掉調(diào)試器時(shí),你很難處在期望的棧幀上。而現(xiàn)在你可以在任意時(shí)刻停止調(diào)試器并讓代碼執(zhí)行到你期望的位置。這很有用,當(dāng)你想執(zhí)行一段從調(diào)試器很難達(dá)到的代碼。試一試!

在viewDidLoad中的NSLog語(yǔ)句處設(shè)置斷點(diǎn)。暫停調(diào)試器,然后再開(kāi)始;app會(huì)命中你剛剛設(shè)置的斷點(diǎn)。現(xiàn)在你已經(jīng)深入到PhotoCollectionViewController方法中了。現(xiàn)在你可以隨心所欲地使用PhotoCollectionViewController實(shí)例了。多么便捷!

注意:如果在調(diào)試器中你不知道哪個(gè)線程是哪個(gè),來(lái)看一下。主線程總是第一個(gè),libdispatch,GCD的協(xié)調(diào)器是第二個(gè)。剩下的線程要看硬件當(dāng)時(shí)在做什么樣的工作。

在調(diào)試器中,輸入:

1
po self.navigationItem.prompt = "WOOT!"

然后繼續(xù)執(zhí)行app。你會(huì)看到如下所示:

 

18.png

通過(guò)這個(gè)方法,你可以更新UI,探查類的屬性,甚至執(zhí)行方法——無(wú)需重啟app來(lái)進(jìn)入特定的工作流狀態(tài)。很巧妙。

下一步?

下載最終的工程

我不想重提,但是你真的應(yīng)該看一下怎樣使用Instruments。如果你想優(yōu)化app,絕對(duì)需要這個(gè)。Instruments可以概述程序中哪些代碼相對(duì)其它代碼執(zhí)行更久。如果你想知道代碼實(shí)際的執(zhí)行時(shí)間,很可能需要一些自制的解決方案。

同時(shí)學(xué)習(xí)如何在Swift中使用NSOperations和NSOperationQueue,一種基于GCD的并發(fā)技術(shù)。實(shí)際上,這是使用GCD的最佳實(shí)踐。NSOperations提供更好的控制,處理最多的并發(fā)操作,在犧牲一定速度的情況下更加面向?qū)ο蟆?/span>

記住,除非你有特別的理由深入底層,你應(yīng)該始終嘗試并堅(jiān)持使用更高層的API。只在你想學(xué)習(xí)更多或做一些非常非常“有趣”的事時(shí)才進(jìn)入到Apple的“暗黑藝術(shù)”(dark art)中探險(xiǎn)。:]

祝你好運(yùn),盡情歡樂(lè)!


發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 麻豆蜜桃在线观看 | 毛片视频在线免费观看 | 亚洲精品动漫在线观看 | 黄色免费高清网站 | 国产精品久久久久久婷婷天堂 | 成人免费观看49www在线观看 | 成年人视频在线免费播放 | 美女黄页网站免费进入 | 天天操天天骑 | 一级黄色片武则天 | 草莓福利视频在线观看 | 小视频免费在线观看 | www噜噜偷拍在线视频 | 水多视频在线观看 | 国产亚洲精品美女久久久 | 999精品久久久 | 日韩精品中文字幕在线观看 | 中文字幕天堂在线 | 国产亚洲欧美日韩在线观看不卡 | 久久国产一二区 | 羞羞视频免费入口网站 | 成人在线影视 | 小视频免费在线观看 | 国产精品免费在线 | 高清国产免费 | 草操影院 | 成人在线视频在线观看 | 韩国一大片a毛片 | 精品一区二区三区欧美 | 久久精品日韩一区 | 国产成人综合在线观看 | 欧美成人一级片 | 综合网天天射 | 精品亚洲一区二区 | 99爱国产精品 | 久久久电影电视剧免费看 | 深夜免费观看视频 | 亚洲尻逼视频 | 国产一级毛片av | 成人毛片网 | 一级一级一级毛片 |