版本記錄
版本號 | 時間 |
---|---|
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)注~~~