這篇文主要想總結(jié)下多線程在swift中的使用,先看下基本概念
進(jìn)程
進(jìn)程指在系統(tǒng)中能獨(dú)立運(yùn)行并作為資源分配的基本單位,它是由一組機(jī)器指令、數(shù)據(jù)和堆棧等組成的,是一個(gè)能獨(dú)立運(yùn)行的活動(dòng)實(shí)體
線程
線程是進(jìn)程的基本執(zhí)行單元,一個(gè)進(jìn)程(程序)的所有任務(wù)都在線程中執(zhí)行。
隊(duì)列
隊(duì)列,又稱為佇列(queue),是先進(jìn)先出(FIFO, First-In-First-Out)的線性表。在具體應(yīng)用中通常用鏈表或者數(shù)組來實(shí)現(xiàn)。隊(duì)列只允許在后端(稱為rear)進(jìn)行插入操作,在前端(稱為front)進(jìn)行刪除操作。隊(duì)列的操作方式和堆棧類似,唯一的區(qū)別在于隊(duì)列只允許新數(shù)據(jù)在后端進(jìn)行添加。
同步/異步
可以這么理解:(知乎)
- 假如你要做兩件事 , 燒水 、 刷牙
- 同步 :你燒水 , 等水燒開了你再去刷牙
- 異步 :你燒水 ,不等水燒開就去刷牙了 , 水燒開了會發(fā)出聲音告訴你(callback) , 然后你再處理水燒開之后的事情
只要你是個(gè)正常人 , 都會選擇第二種 , 當(dāng)然也有特殊情況 , 那個(gè)人喜歡用熱水刷牙
并發(fā)
指兩個(gè)或多個(gè)事件在同一時(shí)間間隔內(nèi)發(fā)生??梢栽谀硹l線程和其他線程之間反復(fù)多次進(jìn)行上下文切換,看上去就好像一個(gè)CPU能夠并且執(zhí)行多個(gè)線程一樣。其實(shí)是偽異步。
線程隊(duì)列中并行/串行
串行隊(duì)列:串行隊(duì)列的特點(diǎn)是隊(duì)列內(nèi)的線程是一個(gè)一個(gè)執(zhí)行,直到結(jié)束。并行隊(duì)列:并行隊(duì)列的特點(diǎn)是隊(duì)列中所有線程的執(zhí)行結(jié)束時(shí)必須是一塊的,隊(duì)列中其他線程執(zhí)行完畢后,會阻塞當(dāng)前線程等待隊(duì)列中其他線程執(zhí)行,然后一塊執(zhí)行完畢。
如何使用
DispatchQueue.global().async {
print("do something in global \(Thread.current)")
DispatchQueue.main.async {
print("do something in main \(Thread.current)")
}
}
這里使用了全局的隊(duì)列執(zhí)行一些任務(wù) , 然后切回主隊(duì)列 , 這里要注意主隊(duì)列是運(yùn)行在主線程上的任務(wù)堆棧 。
自定義隊(duì)列
除了使用全局隊(duì)列外我們還可以使用自定義的隊(duì)列
let q = DispatchQueue(label: "com.felix.felix")
初始化一個(gè)隊(duì)列最簡單的方式就是聲明它的標(biāo)簽 。
async
打開Xcode,新建一個(gè)commandLineTool工程、
打開main.swift
let q = DispatchQueue(label: "com.felix.felix")
q.sync {
(1...5).forEach({ i in
print("?? \(Thread.current) + \(i)")
})
}
q.async {
(6...10).forEach({ i in
print("?? \(Thread.current) + \(i)")
})
}
(11...15).forEach({ i in
print("?? \(Thread.current) + \(i)")
})
先聲明一個(gè)隊(duì)列,使用sync添加一個(gè)同步的任務(wù)輸出1到5,使用async異步輸出6到10,同時(shí)在主線程打印11到15 。
按下command+R運(yùn)行project,
?? <NSThread: 0x103103480>{number = 1, name = main} + 1
?? <NSThread: 0x103103480>{number = 1, name = main} + 2
?? <NSThread: 0x103103480>{number = 1, name = main} + 3
?? <NSThread: 0x103103480>{number = 1, name = main} + 4
?? <NSThread: 0x103103480>{number = 1, name = main} + 5
?? <NSThread: 0x103103480>{number = 1, name = main} + 11
?? <NSThread: 0x103007940>{number = 2, name = (null)} + 6
?? <NSThread: 0x103103480>{number = 1, name = main} + 12
?? <NSThread: 0x103007940>{number = 2, name = (null)} + 7
?? <NSThread: 0x103103480>{number = 1, name = main} + 13
?? <NSThread: 0x103007940>{number = 2, name = (null)} + 8
?? <NSThread: 0x103103480>{number = 1, name = main} + 14
?? <NSThread: 0x103007940>{number = 2, name = (null)} + 9
?? <NSThread: 0x103103480>{number = 1, name = main} + 15
?? <NSThread: 0x103007940>{number = 2, name = (null)} + 10
Program ended with exit code: 0
我們可以看到,??代表的任務(wù)全部是優(yōu)先執(zhí)行的,這說明sync添加的任務(wù)會阻塞當(dāng)前線程,在看到??和??是均勻分部的,這是由于async添加的任務(wù)會默認(rèn)加入由系統(tǒng)管理的線程池,異步執(zhí)行 。
優(yōu)先級 QoS
當(dāng)多個(gè)隊(duì)列同時(shí)執(zhí)行的時(shí)候,系統(tǒng)需要知道哪個(gè)隊(duì)列優(yōu)先級更高,才能優(yōu)先安排計(jì)算資源給他,我們可以這樣定義優(yōu)先級:
let q = DispatchQueue(label: "com.felix.felix", qos: DispatchQoS.background)
初始化的時(shí)候加上qos參數(shù) , qos(quality of service)從字面上理解就是「服務(wù)質(zhì)量」,在swift中是這樣定義的:
public enum QoSClass {
@available(OSX 10.10, iOS 8.0, *)
case background
@available(OSX 10.10, iOS 8.0, *)
case utility
@available(OSX 10.10, iOS 8.0, *)
case `default`
@available(OSX 10.10, iOS 8.0, *)
case userInitiated
@available(OSX 10.10, iOS 8.0, *)
case userInteractive
case unspecified
@available(OSX 10.10, iOS 8.0, *)
public init?(rawValue: qos_class_t)
@available(OSX 10.10, iOS 8.0, *)
public var rawValue: qos_class_t { get }
}
- User Interactive: 和用戶交互相關(guān),比如動(dòng)畫等等優(yōu)先級最高。比如用戶連續(xù)拖拽的計(jì)算
- User Initiated: 需要立刻的結(jié)果,比如push一個(gè)ViewController之前的數(shù)據(jù)計(jì)算
- Utility: 可以執(zhí)行很長時(shí)間,再通知用戶結(jié)果。比如下載一個(gè)文件,給用戶下載進(jìn)度。
- Background: 用戶不可見,比如在后臺存儲大量數(shù)據(jù)
在選擇優(yōu)先級時(shí)可以參考如下判斷 。
- 這個(gè)任務(wù)是用戶可見的嗎?
- 這個(gè)任務(wù)和用戶交互有關(guān)嗎?
- 這個(gè)任務(wù)的執(zhí)行時(shí)間有多少?
- 這個(gè)任務(wù)的最終結(jié)果和UI有關(guān)系嗎?
并發(fā)隊(duì)列
默認(rèn)情況下添加進(jìn)Queue的任務(wù)會串行執(zhí)行 , 先執(zhí)行完一個(gè)再執(zhí)行下一個(gè):
import Foundation
let q = DispatchQueue(label: "com.felix.felix")
q.async {
(1...5).forEach({ i in
print("?? \(Thread.current) + \(i)")
})
}
q.async {
(6...10).forEach({ i in
print("?? \(Thread.current) + \(i)")
})
}
(11...15).forEach({ i in
print("?? \(Thread.current) + \(i)")
})
運(yùn)行看下日志輸出
?? <NSThread: 0x102a081a0>{number = 2, name = (null)} + 1
?? <NSThread: 0x100f046f0>{number = 1, name = main} + 11
?? <NSThread: 0x102a081a0>{number = 2, name = (null)} + 2
?? <NSThread: 0x100f046f0>{number = 1, name = main} + 12
?? <NSThread: 0x102a081a0>{number = 2, name = (null)} + 3
?? <NSThread: 0x100f046f0>{number = 1, name = main} + 13
?? <NSThread: 0x102a081a0>{number = 2, name = (null)} + 4
?? <NSThread: 0x100f046f0>{number = 1, name = main} + 14
?? <NSThread: 0x102a081a0>{number = 2, name = (null)} + 5
?? <NSThread: 0x100f046f0>{number = 1, name = main} + 15
?? <NSThread: 0x102a081a0>{number = 2, name = (null)} + 6
?? <NSThread: 0x102a081a0>{number = 2, name = (null)} + 7
?? <NSThread: 0x102a081a0>{number = 2, name = (null)} + 8
?? <NSThread: 0x102a081a0>{number = 2, name = (null)} + 9
Program ended with exit code: 0
我們可以看到直到??都輸出完畢才會輸出??,有時(shí)候我們想把任務(wù)并行執(zhí)行,怎么辦呢。
可以設(shè)置queue的Attributes。
let q = DispatchQueue(label: "com.felix.felix", attributes: DispatchQueue.Attributes.concurrent)
再運(yùn)行下看看會怎樣。
DispatchWorkItem
有的時(shí)候,對于同一個(gè)操作我們有可能會放在不同的線程中去執(zhí)行,這樣我們就可以把這個(gè)操作用DispatchWorkItem的形式包裹起來,在不同的線程中執(zhí)行 。
import Foundation
let group = DispatchGroup()
let q = DispatchQueue(label: "com.felix.felix", attributes: DispatchQueue.Attributes.concurrent)
let item1 = DispatchWorkItem {
(1...5).forEach({ i in
print("?? \(Thread.current) + \(i)")
})
}
let item2 = DispatchWorkItem {
(6...10).forEach({ i in
print("?? \(Thread.current) + \(i)")
})
}
q.async(execute: item1)
q.async(execute: item2)
(11...15).forEach({ i in
print("?? \(Thread.current) + \(i)")
})
Group 隊(duì)列組
DispatchGroup 可以用來管理一組隊(duì)列,監(jiān)聽所有隊(duì)列的所有任務(wù)都完成的通知,比較常用的就是在一個(gè)頁面請求多個(gè)接口的時(shí)候,全部請求完再刷新UI 。
總結(jié)
總之,使用GCD一方面會提升我們應(yīng)用的性能,給用戶帶來更好的體驗(yàn),不過一定要注意線程安全問題。