iOS與多線程(六) —— GCD之一個簡單應(yīng)用示例(二)

版本記錄

版本號 時間
V1.0 2018.08.20

前言

信號量機制是多線程通信中的比較重要的一部分,對于NSOperation可以設(shè)置并發(fā)數(shù),但是對于GCD就不能設(shè)置并發(fā)數(shù)了,那么就只能靠信號量機制了。接下來這幾篇就會詳細(xì)的說一下并發(fā)機制。感興趣的可以看這幾篇文章。
1. iOS與多線程(一) —— GCD中的信號量及幾個重要函數(shù)
2. iOS與多線程(二) —— NSOperation實現(xiàn)多并發(fā)之創(chuàng)建任務(wù)
3. iOS與多線程(三) —— NSOperation實現(xiàn)多并發(fā)之創(chuàng)建隊列和開啟線程
4. iOS與多線程(四) —— NSOperation的串并行和操作依賴
5. iOS與多線程(五) —— GCD之一個簡單應(yīng)用示例(一)

開始

本篇主要將深入研究高級GCD概念,包括調(diào)度組,取消調(diào)度塊,異步測試技術(shù)和調(diào)度源。

如果您繼續(xù)學(xué)習(xí),您可以從上一篇的示例項目中選擇您上次停下的地方。

運行應(yīng)用程序,點擊+,然后選擇Le Internet以添加互聯(lián)網(wǎng)照片。 您可能會注意到在圖像下載完成之前會彈出下載完成alert消息:

這個就是首先你需要解決的問題。


Dispatch Groups - 調(diào)度組

打開PhotoManager.swift并查看downloadPhotos(withCompletion :)

func downloadPhotos(
    withCompletion completion: BatchPhotoDownloadingCompletionClosure?) {
  var storedError: NSError?
  for address in [PhotoURLString.overlyAttachedGirlfriend,
                  PhotoURLString.successKid,
                  PhotoURLString.lotsOfFaces] {
                    let url = URL(string: address)
                    let photo = DownloadPhoto(url: url!) { _, error in
                      if error != nil {
                        storedError = error
                      }
                    }
                    PhotoManager.shared.addPhoto(photo)
    }
    
    completion?(storedError)
}

傳遞給方法的completion閉包觸發(fā)alert彈窗。您可以在下載照片的for循環(huán)后調(diào)用此方法。在調(diào)用閉包之前,您錯誤地認(rèn)為下載已完成。

您通過調(diào)用DownloadPhoto(url :)開始下載照片。此調(diào)用立即返回,但實際下載是異步發(fā)生的。因此,當(dāng)完成運行時,無法保證所有下載都已完成。

你想要的是downloadPhotos(withCompletion :)在所有照片下載任務(wù)完成后調(diào)用它的completion閉包。如何監(jiān)控這些并發(fā)異步事件來實現(xiàn)這一目標(biāo)?使用當(dāng)前的方法,您不知道任務(wù)何時完成,并且可以按任何順序完成。

好消息!這正是調(diào)度組(dispatch groups )存在的原因。使用調(diào)度組,您可以將多個任務(wù)組合在一起,并等待它們完成,或者在完成后收到通知。任務(wù)可以是異步的或同步的,甚至可以在不同的隊列上運行。

DispatchGroup管理調(diào)度組。你將首先看看它的wait方法。這將阻塞當(dāng)前線程,直到所有組的排隊任務(wù)完成。

PhotoManager.swift中,用以下代碼替換downloadPhotos(withCompletion :)中的代碼:

// 1
DispatchQueue.global(qos: .userInitiated).async {
  var storedError: NSError?

  // 2
  let downloadGroup = DispatchGroup()
  for address in [PhotoURLString.overlyAttachedGirlfriend, 
                  PhotoURLString.successKid,
                  PhotoURLString.lotsOfFaces] {
    let url = URL(string: address)

    // 3
    downloadGroup.enter()
    let photo = DownloadPhoto(url: url!) { _, error in
      if error != nil {
        storedError = error
      }   

      // 4
      downloadGroup.leave()
    }   
    PhotoManager.shared.addPhoto(photo)
  }   

  // 5      
  downloadGroup.wait()

  // 6
  DispatchQueue.main.async {
    completion?(storedError)
  }   
}

以下是代碼逐步執(zhí)行的操作:

  • 1)由于您正在使用阻塞當(dāng)前線程的同步wait方法,因此使用async將整個方法放入后臺隊列以確保不阻止主線程。
  • 2)創(chuàng)建一個新的調(diào)度組。
  • 3)調(diào)用enter()手動通知組任務(wù)已啟動。您必須使用和leave()數(shù)量相等進行調(diào)用enter(),否則您的應(yīng)用程序?qū)⒈罎ⅰ?/li>
  • 4)在這里,您通知小組這項工作已完成。
  • 5)在等待任務(wù)完成時調(diào)用wait()來阻塞當(dāng)前線程。這等待會持續(xù),因為照片創(chuàng)建任務(wù)總是完成。您可以使用wait(timeout :)來指定超時,并在指定時間后等待挽救。
  • 6)此時,您可以保證所有圖像任務(wù)都已完成或超時。然后,您回調(diào)主隊列以運行完成關(guān)閉。

Build并運行應(yīng)用程序。通過Le Internet選項下載照片,并驗證在下載所有圖像之前alert彈窗未顯示。

注意:如果網(wǎng)絡(luò)活動發(fā)生得太快以至于無法識別何時應(yīng)該調(diào)用完成閉包并且您在設(shè)備上運行應(yīng)用程序,則可以通過在iOS的Settings應(yīng)用程序的Developer部分中切換某些網(wǎng)絡(luò)設(shè)置來確保這確實有效。 只需轉(zhuǎn)到Network Link Conditioner部分,啟用它,然后選擇配置文件。Very Bad Network是一個不錯的選擇。如果您在模擬器上運行,則可以使用 Network Link Conditioner included in the Advanced Tools for Xcode來更改網(wǎng)絡(luò)速度。 這是一個很好的工具,因為它會讓你在連接速度低于最佳狀態(tài)時意識到你的應(yīng)用程序會發(fā)生什么。

Dispatch groups是所有類型隊列的很好的選擇。 如果您因為不想保留主線程而同步等待完成所有工作,那么您應(yīng)該注意在主隊列上使用調(diào)度組。 但是,異步模型是一種在多個長時間運行的任務(wù)完成后更新UI的一個不錯的方法,例如網(wǎng)絡(luò)調(diào)用。

您當(dāng)前的解決方案是好的,但一般來說,最好盡可能避免阻塞線程。 您的下一個任務(wù)是重寫相同的方法,以便在完成所有下載后異步通知您。


Dispatch Groups, Take 2 - 調(diào)度組(2)

異步調(diào)度到另一個隊列然后使用wait阻塞工作是不可取的。 幸運的是,有一種更好的方法。 DispatchGroup可以在所有組的任務(wù)完成時通知您。

仍然在PhotoManager.swift中,用以下內(nèi)容替換downloadPhotos(withCompletion :)中的代碼:

// 1
var storedError: NSError?
let downloadGroup = DispatchGroup()
for address in [PhotoURLString.overlyAttachedGirlfriend,
                PhotoURLString.successKid,
                PhotoURLString.lotsOfFaces] {
  let url = URL(string: address)
  downloadGroup.enter()
  let photo = DownloadPhoto(url: url!) { _, error in
    if error != nil {
      storedError = error
    }   
    downloadGroup.leave()
  }   
  PhotoManager.shared.addPhoto(photo)
}   

// 2    
downloadGroup.notify(queue: DispatchQueue.main) {
  completion?(storedError)
}

下面進行詳細(xì)拆分:

  • 1)在這個新實現(xiàn)中,您不需要在異步(async)調(diào)用中包圍該方法,因為您沒有阻塞主線程。
  • 2)notify(queue:work :)用作異步完成閉包。 它在組中沒有剩余項目時運行。 您還指定要計劃在主隊列上調(diào)度完成工作。

這是一種更清潔的方式來處理這個特定的工作,因為它不會阻止任何線程。

Build并運行應(yīng)用程序。 確認(rèn)在下載所有互聯(lián)網(wǎng)照片后仍然顯示下載完成alert彈窗:


Concurrency Looping - 并發(fā)循環(huán)

有了所有這些新工具,你可能應(yīng)該解決所有問題,對吧?。?/p>

看看PhotoManager中的downloadPhotos(withCompletion :)。 您可能會注意到那里有一個for循環(huán),循環(huán)三次迭代并下載三個單獨的圖像。 你的工作是看你是否可以同時運行這個for循環(huán)來嘗試加快速度。

這是DispatchQueue.concurrentPerform(iterations:execute:)的工作。 它與for循環(huán)的工作方式類似,因為它同時執(zhí)行不同的迭代。 它是同步的,只有在完成所有工作后才返回。

在確定給定工作量的最佳迭代次數(shù)時,您必須小心。 每次迭代的許多迭代和少量工作都會產(chǎn)生如此多的開銷,從而抵消了使調(diào)用并發(fā)的任何好處或者優(yōu)勢。 稱為跨步(striding)的技術(shù)可以幫助你。 Striding允許您為每次迭代執(zhí)行多項工作。

什么時候適合使用DispatchQueue.concurrentPerform(iterations:execute :)? 您可以排除串行隊列,因為那里沒有任何好處 - 您也可以使用普通的for循環(huán)。 對于包含循環(huán)的并發(fā)隊列來說,這是一個不錯的選擇,特別是如果您需要跟蹤進度。

PhotoManager.swift中使用以下代碼替換downloadPhotos(withCompletion :)中的代碼:

var storedError: NSError?
let downloadGroup = DispatchGroup()
let addresses = [PhotoURLString.overlyAttachedGirlfriend,
                 PhotoURLString.successKid,
                 PhotoURLString.lotsOfFaces]
let _ = DispatchQueue.global(qos: .userInitiated)
DispatchQueue.concurrentPerform(iterations: addresses.count) { index in
  let address = addresses[index]
  let url = URL(string: address)
  downloadGroup.enter()
  let photo = DownloadPhoto(url: url!) { _, error in
    if error != nil {
      storedError = error
    }
    downloadGroup.leave()
  }
  PhotoManager.shared.addPhoto(photo)
}
downloadGroup.notify(queue: DispatchQueue.main) {
  completion?(storedError)
}

您使用DispatchQueue.concurrentPerform(iterations:execute :)替換前者for循環(huán)以處理并發(fā)循環(huán)。

這個實現(xiàn)包括一段奇怪的代碼:let _ = DispatchQueue.global(qos:.userInitiated)。 調(diào)用此方法會導(dǎo)致GCD使用具有.userInitiated服務(wù)質(zhì)量的隊列進行并發(fā)調(diào)用。

Build并運行應(yīng)用程序。 驗證Internet下載功能是否仍然正常運行:

在設(shè)備上運行此新代碼有時會產(chǎn)生稍微快一些的結(jié)果。但這一切都值得嗎?

實際上,在這種情況下,它是不值得的。原因如下:

  • 與首先運行for循環(huán)相比,您可能創(chuàng)建了更多的并行運行線程的開銷,您應(yīng)該使用DispatchQueue.concurrentPerform(iterations:execute :)來迭代非常大的集合以及適當(dāng)?shù)目缍乳L度。
  • 您創(chuàng)建應(yīng)用程序的時間有限 - 不要浪費時間預(yù)先優(yōu)化您不知道的代碼。如果你要優(yōu)化某些東西,那么優(yōu)化一些值得注意的東西,值得花時間。通過在Instruments中分析您的應(yīng)用程序,找到執(zhí)行時間最長的方法。
  • 通常,優(yōu)化代碼會使您的代碼對您自己以及其他開發(fā)人員變得更加復(fù)雜,確保增加的復(fù)雜度是值得的。

請記住,不要因為優(yōu)化而瘋狂。你只會讓自己和那些不得不研究你的代碼的人變得更難。


Canceling Dispatch Blocks - 取消調(diào)度塊

到目前為止,您還沒有看到允許您取消排隊任務(wù)的代碼。 這是DispatchWorkItem表示的dispatch block objects成為焦點的地方。 請注意,您只能在DispatchWorkItem到達隊列頭部并開始執(zhí)行之前取消它。

讓我們通過從Le Internet開始幾個圖像的下載任務(wù)然后取消其中一些來證明這一點。

仍在PhotoManager.swift中,將downloadPhotos(withCompletion :)中的代碼替換為以下代碼:

var storedError: NSError?
let downloadGroup = DispatchGroup()
var addresses = [PhotoURLString.overlyAttachedGirlfriend,
                 PhotoURLString.successKid,
                 PhotoURLString.lotsOfFaces]

// 1
addresses += addresses + addresses

// 2
var blocks: [DispatchWorkItem] = []

for index in 0..<addresses.count {
  downloadGroup.enter()

  // 3
  let block = DispatchWorkItem(flags: .inheritQoS) {
    let address = addresses[index]
    let url = URL(string: address)
    let photo = DownloadPhoto(url: url!) { _, error in
      if error != nil {
        storedError = error
      }
      downloadGroup.leave()
    }
    PhotoManager.shared.addPhoto(photo)
  }
  blocks.append(block)

  // 4
  DispatchQueue.main.async(execute: block)
}

// 5
for block in blocks[3..<blocks.count] {

  // 6
  let cancel = Bool.random()
  if cancel {

    // 7
    block.cancel()

    // 8
    downloadGroup.leave()
  }
}

downloadGroup.notify(queue: DispatchQueue.main) {
  completion?(storedError)
}

以下是逐步完成上述代碼的步驟:

  • 1)您展開地址(addresses)數(shù)組以保存每個圖像的三個副本。
  • 2)初始化塊(blocks)數(shù)組以保存調(diào)度塊對象供以后使用。
  • 3)您創(chuàng)建一個新的DispatchWorkItem。傳入flags參數(shù)以指定塊應(yīng)從您將其分派到的隊列中繼承其Quality of Service類。然后,定義要在閉包中執(zhí)行的工作。
  • 4)您將塊異步調(diào)度到主隊列。對于此示例,使用主隊列可以更輕松地取消選擇塊,因為它是一個串行隊列。設(shè)置調(diào)度塊的代碼已在主隊列上執(zhí)行,因此可以保證下載塊將在以后執(zhí)行。
  • 5)通過切片塊blocks數(shù)組跳過前三個下載塊。
  • 6)在這里,您使用Bool.random()在true和false之間隨機選擇。這就像擲硬幣一樣。
  • 7)如果隨機值為true,則取消該塊。這只能取消仍在隊列中但尚未開始執(zhí)行的塊。您無法在執(zhí)行過程中取消塊。
  • 8)在這里,您需要記住從調(diào)度組中刪除已取消的塊。

Build并運行應(yīng)用程序,然后從Le Internet添加圖像。你會看到該應(yīng)用程序現(xiàn)在下載了三個以上的圖像。每次重新運行應(yīng)用程序時,額外圖像的數(shù)量都會發(fā)生變化。您在開始之前取消隊列中的一些其他圖像下載。

這是一個特意做的例子,但它很好地說明了如何使用和取消調(diào)度塊。

調(diào)度塊可以執(zhí)行更多操作,因此請務(wù)必查看Apple's documentation


Miscellaneous GCD Fun - 其他GCD樂趣

還有更多! 這里有一些額外的功能。 雖然您幾乎不會頻繁使用這些工具,但它們在正確的情況下可以提供極大的幫助。

1. Testing Asynchronous Code - 測試異步代碼

這可能聽起來像一個瘋狂的想法,但你知道Xcode有測試功能嗎? 我知道,有時我喜歡假裝它不存在,但在代碼中構(gòu)建復(fù)雜的關(guān)系時,編寫和運行測試很重要。

Xcode測試都包含在XCTestCase的子類中,并且是簽名以test開頭的任何方法。 Tests在主線程上運行,因此您可以假設(shè)每個測試都以串行方式進行。

一旦給定的測試方法完成,Xcode就會認(rèn)為測試已經(jīng)完成并繼續(xù)進行下一個測試。 這意味著在下一個測試運行時,前一個測試的任何異步代碼都將繼續(xù)運行。

網(wǎng)絡(luò)代碼通常是異步的,因為您不希望在執(zhí)行網(wǎng)絡(luò)提取時阻塞主線程。 這與測試方法完成后測試完成的事實相結(jié)合,可能使測試網(wǎng)絡(luò)代碼變得困難。

我們來簡要介紹一下如何使用信號量(semaphores)來測試異步代碼。

2. Semaphores - 信號量

信號量是一個老式的線程概念,由Edsger W. Dijkstra引入。 信號量是一個復(fù)雜的話題,因為它們建立在操作系統(tǒng)功能的復(fù)雜性之上。

如果您想了解有關(guān)信號量的更多信息,請查看有關(guān)信號量理論的detailed discussion。 如果你是學(xué)術(shù)類型,你可能想看看Dining Philosophers Problem,這是一個使用信號量的經(jīng)典軟件開發(fā)問題。

打開GooglyPuffTests.swift并使用以下代碼替換downloadImageURL(withString :)中的代碼:

let url = URL(string: urlString)

// 1
let semaphore = DispatchSemaphore(value: 0)
let _ = DownloadPhoto(url: url!) { _, error in
  if let error = error {
    XCTFail("\(urlString) failed. \(error.localizedDescription)")
  }

  // 2
  semaphore.signal()
}
let timeout = DispatchTime.now() + .seconds(defaultTimeoutLengthInSeconds)

// 3
if semaphore.wait(timeout: timeout) == .timedOut {
  XCTFail("\(urlString) timed out")
} 

以下是信號量在上面的代碼中的工作原理:

  • 1)您創(chuàng)建一個信號量并設(shè)置其起始值。 這代表可以訪問信號量而不需要增加信號量的事物數(shù)量(請注意,遞增信號量稱為發(fā)信號)。
  • 2)您在完成閉包中發(fā)出信號量的信號。 這會增加信號量計數(shù),并表示信號量可供其他需要它的資源使用。
  • 3)你等待信號量,給定超時。 此調(diào)用會阻塞當(dāng)前線程,直到信號量發(fā)出信號。 此函數(shù)的非零返回碼表示超時時間已到期。 在這種情況下,測試失敗,因為網(wǎng)絡(luò)返回的時間不應(yīng)超過10秒。

如果您具有默認(rèn)鍵綁定,則從菜單中選擇Product ? Test或使用Command-U運行測試。 他們都應(yīng)該及時取得成功:

停止連接并再次運行測試。 如果您在設(shè)備上運行,請將其置于飛行(airplane)模式。 如果您在模擬器上運行,則只需關(guān)閉連接即可。 測試在10秒后完成,可以看見失敗結(jié)果。 很棒,它有效!

這些都是相當(dāng)簡單的測試,但如果您正在與服務(wù)器團隊合作,這些基本測試可以防止一輪指責(zé)誰應(yīng)該為最新的網(wǎng)絡(luò)問題負(fù)責(zé)。

注意:在代碼中實現(xiàn)異步測試時,請先查看XCTWaiter,然后再轉(zhuǎn)到這些低級API。 XCTWaiter的API更好,為異步測試提供了許多強大的技術(shù)。

3. Dispatch Sources

Dispatch sources是GCD特別有趣的功能。 您可以使用調(diào)度源來監(jiān)視某種類型的事件。 事件可以包括Unix信號,文件描述符,Mach端口,VFS節(jié)點和其他模糊的東西。

在設(shè)置調(diào)度源時,您可以告訴它要監(jiān)視的事件類型以及應(yīng)在其上執(zhí)行事件處理程序塊的調(diào)度隊列。 然后,您將事件處理程序分配給調(diào)度源。

創(chuàng)建后,調(diào)度源將以掛起狀態(tài)啟動。 這允許您執(zhí)行所需的任何其他配置,例如設(shè)置事件處理程序。 配置調(diào)度源后,必須將其恢復(fù)以開始處理事件。

在本文中,您將以一種相當(dāng)特殊的方式來嘗試使用調(diào)度源:監(jiān)視應(yīng)用程序何時進入調(diào)試模式(debug mode)。

打開PhotoCollectionViewController.swift并在backgroundImageOpacity全局屬性聲明下面添加以下內(nèi)容:

// 1
#if DEBUG

  // 2
  var signal: DispatchSourceSignal?

  // 3
  private let setupSignalHandlerFor = { (_ object: AnyObject) in
    let queue = DispatchQueue.main

    // 4
    signal =
      DispatchSource.makeSignalSource(signal: SIGSTOP, queue: queue)
        
    // 5
    signal?.setEventHandler {
      print("Hi, I am: \(object.description!)")
    }

    // 6
    signal?.resume()
  }
#endif

下面進行詳細(xì)說明:

  • 1)您只能在DEBUG模式下編譯此代碼,以防止“interested parties”獲得對您的應(yīng)用程序的大量洞察。DEBUG是通過在Project Settings -> Build Settings -> Swift Compiler - Custom Flags -> Other Swift Flags -> Debug下添加-D DEBUG來定義的。它應(yīng)該已經(jīng)在啟動項目中設(shè)置。
  • 2)您聲明一個DispatchSourceSignal類型的signal變量,用于監(jiān)視Unix信號。
  • 3)您創(chuàng)建一個分配給setupSignalHandlerFor全局變量的塊,您將用于一次性設(shè)置調(diào)度源。
  • 4)在這里設(shè)置signal。您表示您有興趣監(jiān)視SIGSTOP Unix信號并處理主隊列上收到的事件 - 您很快就會發(fā)現(xiàn)原因。
  • 5)如果成功創(chuàng)建了調(diào)度源,則會注冊每當(dāng)收到SIGSTOP信號時調(diào)用的事件處理程序閉包。您的處理程序打印包含類描述的消息。
  • 6)默認(rèn)情況下,所有源都以掛起狀態(tài)啟動。在這里,您告訴調(diào)度源恢復(fù),以便它可以開始監(jiān)視事件。

將以下代碼添加到對super.viewDidLoad()調(diào)用正下方的viewDidLoad()中:

#if DEBUG
  setupSignalHandlerFor(self)
#endif

此代碼調(diào)用調(diào)度源的初始化代碼。

Build并運行應(yīng)用程序。 通過點擊Xcode調(diào)試器中的暫停然后播放按鈕,暫停程序執(zhí)行并立即恢復(fù)應(yīng)用程序:

看一下控制臺,你會看到這些輸出:

Hi, I am: <GooglyPuff.PhotoCollectionViewController: 0x7fbf0af08a10>

你的app現(xiàn)在可以調(diào)試了! 這真是太棒了,但是你會在現(xiàn)實生活中如何使用它?

您可以使用它來調(diào)試對象并在您恢復(fù)應(yīng)用程序時顯示數(shù)據(jù)。 當(dāng)惡意攻擊者將調(diào)試器附加到您的應(yīng)用程序時,您還可以為應(yīng)用程序提供自定義安全邏輯以保護自身(或用戶的數(shù)據(jù))。

一個有趣的想法是使用此方法作為堆棧跟蹤工具來查找要在調(diào)試器中操作的對象。

想想這種情況一秒鐘。 當(dāng)你突然停止調(diào)試器時,你幾乎從不在所需的堆棧幀上。 現(xiàn)在,您可以隨時停止調(diào)試器,并在所需位置執(zhí)行代碼。 如果您想在應(yīng)用程序中從調(diào)試器訪問繁瑣的點執(zhí)行代碼,這非常有用。 試試看!

在剛剛添加的setupSignalHandlerFor塊內(nèi)的print()語句上放置一個斷點。

在調(diào)試器中暫停,然后重新開始。 該應(yīng)用程序?qū)⑦_到您添加的斷點。 您現(xiàn)在深入了解PhotoCollectionViewController方法的深度。 現(xiàn)在,您可以訪問PhotoCollectionViewController的實例到您的內(nèi)容。 非常方便!

注意:如果您還沒有注意到調(diào)試器中有哪些線程,請立即查看它們。 主線程將始終是第一個線程,然后是libdispatch,GCD的協(xié)調(diào)器,作為第二個線程。 之后,線程計數(shù)和剩余線程取決于應(yīng)用程序到達斷點時硬件正在執(zhí)行的操作。

在控制臺輸入下面內(nèi)容:

expr object.navigationItem.prompt = "WOOT!"

Xcode調(diào)試器有時可能不合作。 如果你收到消息:

error: use of unresolved identifier 'self'

然后你必須以艱難的方式解決LLDB中的錯誤。 首先記下調(diào)試區(qū)域中object的地址:

po object

然后通過在調(diào)試器中運行以下命令手動將值轉(zhuǎn)換為所需的類型,將0xHEXADDRESS替換為輸出的地址:

expr let $vc = unsafeBitCast(0xHEXADDRESS, to: GooglyPuff.PhotoCollectionViewController.self)
expr $vc.navigationItem.prompt = "WOOT!"

如果這不起作用,幸運的是 - 你在LLDB中遇到了另一個錯誤! 在這種情況下,您可能需要再次嘗試構(gòu)建和運行應(yīng)用程序。

成功運行此命令后,請繼續(xù)執(zhí)行應(yīng)用程序。 你會看到以下內(nèi)容:

使用此方法,您可以對UI進行更新,查詢類的屬性,甚至執(zhí)行方法 - 所有這些都可以在不必重新啟動應(yīng)用程序的情況下進入特殊的工作流狀態(tài)。 很簡約。

除了GCD,通常,如果您使用簡單的fire-and-forget任務(wù),最好使用GCD。 Operation提供了更好的控制,處理最大并發(fā)操作的實現(xiàn),以及以速度為代價的更加面向?qū)ο蟮姆独?/p>

請記住,除非您有特定的理由要求降低,否則請始終嘗試使用更高級別的API。

后記

本篇主要講述了,感興趣的給個贊或者關(guān)注~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,363評論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,497評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,305評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,962評論 1 311
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,727評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,193評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,257評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,411評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,945評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,777評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,978評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,519評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,216評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,657評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,960評論 2 373

推薦閱讀更多精彩內(nèi)容