文章主要記錄了iOS中多線程的基礎概念及使用方法,在此作一個記錄。一是加深印象,之後本身使用時也能夠方便查找及複習,二是在本身的學習過程當中,總有大牛的文章做爲引導,但願本身也能給須要這方面知識的人一些幫助。git
關於這篇文章的Demo能夠去個人github中MultiThreadDemo查看源碼,若有不當之處,但願你們指出。程序員
GCD方面的知識點,後續會繼續更新。。。github
優勢api
缺點數組
實際上,使用多線程,因爲會開線程,必然就會消耗性能,可是卻能夠提升用戶體驗。因此,綜合考慮,在保證良好的用戶體驗的前提下,能夠適當地開線程。bash
在iOS中每一個進程啓動後都會創建一個主線程(UI線程)。因爲在iOS中除了主線程,其餘子線程是獨立於Cocoa Touch的,因此只有主線程能夠更新UI界面。iOS中多線程使用並不複雜,關鍵是如何控制好各個線程的執行順序、處理好資源競爭問題。多線程
接下來就介紹一下iOS常見的幾種多線程實現方式。併發
這裏介紹Thread的三種建立方式。下方三中建立方式中的Target類爲:app
class Receiver: NSObject {
@objc func runThread() {
print(Thread.current)
}
}
複製代碼
// 1.建立線程
let thread_one = Thread(target: Receiver(), selector: #selector(Receiver.runThread), object: nil)
let thread_two = Thread {
// TODO
}
// 2.啓動線程
thread_one.start()
thread_two.start()
複製代碼
// 建立線程後自動啓動線程
Thread.detachNewThread {
// TODO
}
Thread.detachNewThreadSelector(#selector(Receiver.runThread), toTarget: Receiver(), with: nil)
複製代碼
let obj = Receiver()
// 隱式建立並啓動線程
obj.performSelector(inBackground: #selector(obj.runThread), with: nil)
複製代碼
// 去主線程執行指定方法
performSelector(onMainThread: Selector, with: Any?, waitUntilDone: Bool, modes: [String]?)
// 去指定線程執行方法
perform(aSelector: Selector, on: Thread, with: Any?, waitUntilDone: Bool, modes: [String]?)
複製代碼
設置線程優先級時,接收一個Double類型。異步
數值範圍爲:0.0 ~ 1.0。
對於新建立的thread來講,Priority的值通常是 0.5。可是,由於優先級是由系統內核決定的,並不能保證這個值會是什麼。
var threadPriority: Double { get set }
複製代碼
與線程狀態及生命週期相關的函數:
// - 啓動線程的方法,進入就緒狀態等待CPU調用
func start()
// - 阻塞(暫停)線程方法,進入阻塞狀態
class func sleep(until date: Date)
class func sleep(forTimeInterval ti: TimeInterval)
// - 取消線程的操做,在線程執行完當前操做後,不會再繼續執行任務
func cancel()
// - 強制中止線程,進入死亡狀態
class func exit()
複製代碼
cancel():方法並非當即取消當前線程,而是更改線程的狀態,以指示它應該退出。
exit():應該避免調用此方法,由於它不會讓線程有機會清理它在執行期間分配的任何資源。
系統還定義了幾個NSNotification。若你對當前線程狀態的改變感興趣,能夠訂閱這幾個通知:
// 當除了主線程外的最後一個線程退出時
static let NSDidBecomeSingleThreaded: NSNotification.Name
// 當線程接收到exit()消息時
static let NSThreadWillExit: NSNotification.Name
// 當建立第一個除主線程外的子線程時發佈,然後再建立子線程時不會再發出通知。
// 通知的觀察者的通知方法在主線程調用
NSWillBecomeMultiThreaded: NSNotification.Name
複製代碼
// 獲取主線程
Thread.main
// 獲取當前線程
Thread.current
// 獲取當前線程狀態
Thread.current.isCancelled
Thread.current.isFinished
Thread.current.isFinished
複製代碼
Operation是一個抽象類,能夠用來封裝一個任務,其中包含代碼邏輯和數據。由於Operation是抽象類,因此編寫代碼時不能直接使用,要使用它的子類,系統默認提供的有NSInvocationOperation(Swift中不可用)和BlockOperation。
OperationQueue(操做隊列)是用來控制一系列操做對象執行的。操做對象被添加進隊列後,一直存在到操做被取消或者執行完成。隊列裏的操做對象執行的順序由操做的優先級和操做之間的依賴決定。一個應用裏能夠建立多個隊列進行操做處理。
由Operation 和 OperationQueue的介紹能夠獲得使用步驟:
以後呢,系統就會自動將OperationQueue的Operation取出來,在新線程中執行操做。
默認是不會開啓線程的,只會在當前的線程中執行操做,能夠經過Operation和OperationQueue實現多線程。
// 1.建立 NSInvocationOperation 對象
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
// 2.調用 start 方法開始執行操做
// 不會開啓線程
[op start];
複製代碼
BlockOperation 是否開啓新線程,取決於操做的個數。若是添加的操做的個數多,就會自動開啓新線程。固然開啓的線程數是由系統來決定的。
// 1. 建立BlockOperation對象,並封裝操做
let op = BlockOperation.init {
print("init + \(Thread.current)")
}
// 2. 調用 start 方法開始執行操做
op.start()
複製代碼
默認狀況下,Operation的子類是同步執行的,若是要建立一個可以併發的子類,咱們可能須要重寫一些方法。
複製代碼
OperationQueue 一共有兩種隊列:主隊列、自定義隊列。其中自定義隊列同時包含了串行、併發功能。下邊是主隊列、自定義隊列的基本建立方法和特色。
// 主隊列獲取方法
let mainQueue = OperationQueue.main
// 自定義隊列建立方法
let queue = OperationQueue()
複製代碼
Operation 須要配合 OperationQueue來實現多線程。咱們須要將建立好的操做加入到隊列中去。有兩種方法:
將建立好的Operation或其子類的實例對象直接添加。
直接經過block的方式添加一個操做至隊列中。
OperationQueue 建立的自定義隊列同時具備串行、併發功能。它的串行功能是經過屬性 最大併發操做數—maxConcurrentOperationCount用來控制一個特定隊列中能夠有多少個操做同時參與併發執行。
注意:這裏 maxConcurrentOperationCount控制的不是併發線程的數量,而是一個隊列中同時能併發執行的最大操做數。並且一個操做也並不是只能在一個線程中運行。
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.addOperation {
sleep(1)
print("1---\(Thread.current)----\(Date.timeIntervalSinceReferenceDate)")
}
queue.addOperation {
sleep(1)
print("2---\(Thread.current)----\(Date.timeIntervalSinceReferenceDate)")
}
queue.addOperation {
sleep(1)
print("3---\(Thread.current)----\(Date.timeIntervalSinceReferenceDate)")
}
queue.addOperation {
sleep(1)
print("4---\(Thread.current)----\(Date.timeIntervalSinceReferenceDate)")
}
-----最大併發操做數爲1,輸出結果:------
1---<NSThread: 0x600001ddc200>{number = 5, name = (null)}----576945144.766482
2---<NSThread: 0x600001dd0280>{number = 6, name = (null)}----576945145.775298
3---<NSThread: 0x600001dfbd00>{number = 4, name = (null)}----576945146.775842
4---<NSThread: 0x600001dd0280>{number = 6, name = (null)}----576945147.779273
-----最大併發操做數爲3,輸出結果:------
2---<NSThread: 0x6000018dc0c0>{number = 5, name = (null)}----576945253.401897
1---<NSThread: 0x6000018c5d00>{number = 7, name = (null)}----576945253.401891
3---<NSThread: 0x6000018ca540>{number = 6, name = (null)}----576945253.401913
4---<NSThread: 0x6000018dc100>{number = 8, name = (null)}----576945254.403032
複製代碼
上方輸出的結果中,分析線程及輸出時間能夠看出:從當最大併發操做數爲1時,操做是按順序串行執行的。當最大操做併發數爲3時,有3個操做是併發執行的,延遲1s後執行另外一個。而開啓線程數量是由系統決定的,不須要咱們來管理。
Operation 提供了3個接口供咱們管理和查看依賴。
// 添加依賴,使當前操做依賴於操做 op 的完成。
func addDependency(_ op: Operation)
// 移除依賴,取消當前操做對操做 op 的依賴。
func removeDependency(_ op: Operation)
// 必須在當前對象開始執行以前完成執行的操做對象數組。
var dependencies: [Operation] { get }
複製代碼
經過添加操做依賴,不管運行幾回,其結果都是 op2 先執行,op1 後執行。
let queue = OperationQueue()
let op1 = BlockOperation {
print("op1")
}
let op2 = BlockOperation {
print("op2")
}
op1.addDependency(op2)
queue.addOperation(op1)
queue.addOperation(op2)
----輸出結果:----
op2
op1
複製代碼
OperationQueue 提供了queuePriority(優先級)屬性,queuePriority屬性適用於同一操做隊列中的操做,不適用於不一樣操做隊列中的操做。默認狀況下,全部新建立的操做對象優先級都是normal。可是咱們能夠經過賦值來改變當前操做在同一隊列中的執行優先級。
// 優先級的取值
public enum QueuePriority : Int {
case veryLow
case low
case normal // default value
case high
case veryHigh
}
複製代碼
對於添加到隊列中的操做,首先進入準備就緒的狀態(就緒狀態取決於操做之間的依賴關係),而後進入就緒狀態的操做的開始執行順序(非結束執行順序)由操做之間相對的優先級決定(優先級是操做對象自身的屬性)。
理解了進入就緒狀態的操做,那麼咱們就理解了queuePriority 屬性的做用對象。
let queue = OperationQueue()
let op = BlockOperation {
print("異步操做 -- \(Thread.current)")
// 回到主線程
OperationQueue.main.addOperation({
print("回到主線程了 -- \(Thread.current)")
})
}
queue.addOperation(op)
-----輸出結果:-----
異步操做 -- <NSThread: 0x60000102f540>{number = 3, name = (null)}
回到主線程了 -- <NSThread: 0x60000100d680>{number = 1, name = main}
複製代碼
1. 取消操做的方法
* func cancel() 可取消操做,實質是標記 isCancelled 狀態。
2. 判斷操做狀態的方法
* isFinished 判斷操做是否已經結束。
* isCancelled 判斷操做是否已經標記爲取消。
* isExecuting 判斷操做是否正在在運行。
* isAsynchronous 判斷操做是否異步執行其任務。
* isReady 判斷操做是否處於準備就緒狀態,這個值和操做的依賴關係相關。
3. 操做同步
* func waitUntilFinished() 阻塞當前線程,直到該操做結束。可用於線程執行順序的同步。
* completionBlock: (() -> Void)? 會在當前操做執行完畢時執行 completionBlock。
複製代碼
1. 取消/暫停/恢復操做
* func cancelAllOperations() 能夠取消隊列的全部操做。
* isSuspended 判斷及設置隊列是否處於暫停狀態。true爲暫停狀態,false爲恢復狀態。
2. 操做同步
* func waitUntilAllOperationsAreFinished() 阻塞當前線程,直到隊列中的操做所有執行完畢。
3. 添加/獲取操做
* func addOperations(_ ops: [Operation], waitUntilFinished wait: Bool) 向隊列中添加操做數組,wait 標誌是否阻塞當前線程直到全部操做結束
* operations 當前在隊列中的操做數組(某個操做執行結束後會自動從這個數組清除)。
* operationCount 當前隊列中的操做數。
4. 獲取隊列
* current 獲取當前隊列,若是當前線程不是在 OperationQueue 上運行則返回 nil。
* main 獲取主隊列。
複製代碼
注意:
- 這裏的暫停和取消(包括操做的取消和隊列的取消)並不表明能夠將當前的操做當即取消,而是噹噹前的操做執行完畢以後再也不執行新的操做。
- 暫停和取消的區別就在於:暫停操做以後還能夠恢復操做,繼續向下執行;而取消操做以後,全部的操做就清空了,沒法再接着執行剩下的操做。