1. 并發隊列 + 同步任務
注意是主線程執行,要避免UI堵塞問題
let queue = DispatchQueue(label: "syncConcurrent", attributes: .concurrent)
queue.sync {
for i in 0...2 {
Thread.sleep(forTimeInterval: 0.5)
print("Sync 1 \(i) [\(Thread.current)]")
}
}
queue.sync {
for i in 0...2 {
Thread.sleep(forTimeInterval: 0.5)
print("Sync 2 \(i) [\(Thread.current)]")
}
}
queue.sync {
for i in 0...2 {
Thread.sleep(forTimeInterval: 0.5)
print("Sync 3 \(i) [\(Thread.current)]")
}
}
print("All done")
輸出:只有一條線程(主線程),所有任務依次有序執行
Sync 1 0 [<_NSMainThread: 0x600001d18380>{number = 1, name = main}]
Sync 1 1 [<_NSMainThread: 0x600001d18380>{number = 1, name = main}]
Sync 1 2 [<_NSMainThread: 0x600001d18380>{number = 1, name = main}]
Sync 2 0 [<_NSMainThread: 0x600001d18380>{number = 1, name = main}]
Sync 2 1 [<_NSMainThread: 0x600001d18380>{number = 1, name = main}]
Sync 2 2 [<_NSMainThread: 0x600001d18380>{number = 1, name = main}]
Sync 3 0 [<_NSMainThread: 0x600001d18380>{number = 1, name = main}]
Sync 3 1 [<_NSMainThread: 0x600001d18380>{number = 1, name = main}]
Sync 3 2 [<_NSMainThread: 0x600001d18380>{number = 1, name = main}]
All done
2. 并發隊列 + 異步執行
let queue = DispatchQueue(label: "asyncConcurrent", attributes: .concurrent)
queue.async {
for i in 0...2 {
Thread.sleep(forTimeInterval: 0.5)
print("Async 1 \(i) [\(Thread.current)]")
}
}
queue.async {
for i in 0...2 {
Thread.sleep(forTimeInterval: 0.5)
print("Async 2 \(i) [\(Thread.current)]")
}
}
queue.async {
for i in 0...2 {
Thread.sleep(forTimeInterval: 0.5)
print("Async 3 \(i) [\(Thread.current)]")
}
}
print("All done")
輸出:多(3)條線程,線程間(無序)交替執行加入隊列中的任務(有序)
All done
Async 3 0 [<NSThread: 0x600002b7e000>{number = 6, name = (null)}]
Async 2 0 [<NSThread: 0x600002b702c0>{number = 3, name = (null)}]
Async 1 0 [<NSThread: 0x600002b7e4c0>{number = 7, name = (null)}]
Async 2 1 [<NSThread: 0x600002b702c0>{number = 3, name = (null)}]
Async 3 1 [<NSThread: 0x600002b7e000>{number = 6, name = (null)}]
Async 1 1 [<NSThread: 0x600002b7e4c0>{number = 7, name = (null)}]
Async 2 2 [<NSThread: 0x600002b702c0>{number = 3, name = (null)}]
Async 1 2 [<NSThread: 0x600002b7e4c0>{number = 7, name = (null)}]
Async 3 2 [<NSThread: 0x600002b7e000>{number = 6, name = (null)}]
3. 串行隊列 + 同步任務
let queue = DispatchQueue(label: "syncSerial")
queue.sync {
for i in 0...2 {
Thread.sleep(forTimeInterval: 0.5)
print("Sync Serial: 1 \(i) [\(Thread.current)]")
}
}
queue.sync {
for i in 0...2 {
Thread.sleep(forTimeInterval: 0.5)
print("Sync Serial: 2 \(i) [\(Thread.current)]")
}
}
queue.sync {
for i in 0...2 {
Thread.sleep(forTimeInterval: 0.5)
print("Sync Serial: 3 \(i) [\(Thread.current)]")
}
}
print("All done")
輸出:主線程+任務有序
Sync Serial: 1 0 [<_NSMainThread: 0x6000005f4000>{number = 1, name = main}]
Sync Serial: 1 1 [<_NSMainThread: 0x6000005f4000>{number = 1, name = main}]
Sync Serial: 1 2 [<_NSMainThread: 0x6000005f4000>{number = 1, name = main}]
Sync Serial: 2 0 [<_NSMainThread: 0x6000005f4000>{number = 1, name = main}]
Sync Serial: 2 1 [<_NSMainThread: 0x6000005f4000>{number = 1, name = main}]
Sync Serial: 2 2 [<_NSMainThread: 0x6000005f4000>{number = 1, name = main}]
Sync Serial: 3 0 [<_NSMainThread: 0x6000005f4000>{number = 1, name = main}]
Sync Serial: 3 1 [<_NSMainThread: 0x6000005f4000>{number = 1, name = main}]
Sync Serial: 3 2 [<_NSMainThread: 0x6000005f4000>{number = 1, name = main}]
All done
4. 串行隊列 + 異步任務
let queue = DispatchQueue(label: "asyncSerial")
queue.async {
for i in 0...2 {
Thread.sleep(forTimeInterval: 0.5)
print("Async Serial: 1 \(i) [\(Thread.current)]")
}
}
queue.async {
for i in 0...2 {
Thread.sleep(forTimeInterval: 0.5)
print("Async Serial: 2 \(i) [\(Thread.current)]")
}
}
queue.async {
for i in 0...2 {
Thread.sleep(forTimeInterval: 0.5)
print("Async Serial: 3 \(i) [\(Thread.current)]")
}
}
print("All done")
輸出: 一條線程(非主線程)+任務有序
Async Serial: 1 0 [<NSThread: 0x600000869a00>{number = 6, name = (null)}]
Async Serial: 1 1 [<NSThread: 0x600000869a00>{number = 6, name = (null)}]
Async Serial: 1 2 [<NSThread: 0x600000869a00>{number = 6, name = (null)}]
Async Serial: 2 0 [<NSThread: 0x600000869a00>{number = 6, name = (null)}]
Async Serial: 2 1 [<NSThread: 0x600000869a00>{number = 6, name = (null)}]
Async Serial: 2 2 [<NSThread: 0x600000869a00>{number = 6, name = (null)}]
Async Serial: 3 0 [<NSThread: 0x600000869a00>{number = 6, name = (null)}]
Async Serial: 3 1 [<NSThread: 0x600000869a00>{number = 6, name = (null)}]
Async Serial: 3 2 [<NSThread: 0x600000869a00>{number = 6, name = (null)}]
5. GCD Group
func request(task: Int, time: TimeInterval, completion: @escaping ()->Void) {
DispatchQueue.global(qos: .background).async {
print("Requesting task: \(task) ... started")
Thread.sleep(forTimeInterval: time)
print("Requesting task: \(task) ... finished")
DispatchQueue.main.async(execute: completion)
}
}
let group = DispatchGroup()
for i in 1...3 {
group.enter()
request(task: i, time: Double(3-i)) {
group.leave()
}
}
group.notify(queue: .main) { [weak self] in
print("All requests are finished!")
self?.view.backgroundColor = .systemGreen
}
輸出:
Requesting task: 1 ... started
Requesting task: 3 ... started
Requesting task: 2 ... started
Requesting task: 3 ... finished
Requesting task: 2 ... finished
Requesting task: 1 ... finished
All requests are finished!
wait()
阻塞group
線程
for i in 1...3 {
/// 等待一秒再執行下一個任務
let _ = group.wait(timeout: .now() + .seconds(1))
group.enter()
request(task: i, time: Double(3-i)) {
group.leave()
}
}
輸出:
Requesting task: 1 ... started
Requesting task: 2 ... started
Requesting task: 3 ... started
Requesting task: 3 ... finished
Requesting task: 2 ... finished
Requesting task: 1 ... finished
All requests are finished!
串行隊列執行異步group任務
let group = DispatchGroup()
let queue = DispatchQueue(label: "requestTaskSerial", qos: .background)
for i in 1...3 {
group.enter()
queue.async(group: group){ [weak self] in
self?.request(task: i, time: 1) {
group.leave()
}
}
}
group.notify(queue: .main) { [weak self] in
print("All requests are finished!")
self?.view.backgroundColor = .systemGreen
}
輸出:
Requesting task: 1 ... started
Requesting task: 2 ... started
Requesting task: 3 ... started
Requesting task: 1 ... finished
Requesting task: 2 ... finished
Requesting task: 3 ... finished
All requests are finished!
Semaphore
DispatchSemaphore provides an efficient implementation of a traditional counting semaphore, which can be used to control access to a resource across multiple execution contexts.
DispatchSemaphore是傳統計數信號量的封裝,用來控制資源被多任務訪問的情況。
let group = DispatchGroup()
let queue = DispatchQueue(label: "requestTaskSerial", qos: .background, attributes: .concurrent)
/*
// 創建一個新的信號量,參數value代表信號量資源池的初始數量。
// value < 0, 返回NULL
// value = 0, 多線程在等待某個特定線程的結束。
// value > 0, 資源數量,可以由多個線程使用。
*/
let semaphore = DispatchSemaphore(value: 2)
for i in 1...3 {
group.enter()
queue.async(group: group){ [weak self] in
semaphore.wait()
self?.request(task: i, time: Double(3-i)) {
group.leave()
semaphore.signal()
}
}
}
group.notify(queue: .main) { [weak self] in
print("All requests are finished!")
self?.view.backgroundColor = .systemGreen
}
輸出:同時最多只能有2個異步任務在執行
Requesting task: 3 ... started
Requesting task: 3 ... finished
Requesting task: 1 ... started
Requesting task: 2 ... started
Requesting task: 2 ... finished
Requesting task: 1 ... finished
All requests are finished!
Barrier
barrier翻譯過來就是屏障。在一個并行queue里,很多時候,我們提交一個新的任務需要這樣做。
- queue里已有任務執行完了新任務才開始
- 新任務開始后提交的任務都要等待新任務執行完畢才能繼續執行
特別注意:
以barrier flag提交的任務能夠保證其在并行隊列執行的時候,是唯一的一個任務。
(只對自己創建的隊列有效,對gloablQueue無效)
let group = DispatchGroup()
let queue = DispatchQueue(label: "requestTaskSerial", qos: .background, attributes: .concurrent)
for i in 1...3 {
group.enter()
queue.async(flags: .barrier){
print("Requesting task: \(i) ... started")
Thread.sleep(forTimeInterval: Double(3-i))
print("Requesting task: \(i) ... finished")
group.leave()
}
}
group.notify(queue: .main) { [weak self] in
print("All requests are finished!")
self?.view.backgroundColor = .systemGreen
}
輸出:類似同步任務順序執行
Requesting task: 1 ... started
Requesting task: 1 ... finished
Requesting task: 2 ... started
Requesting task: 2 ... finished
Requesting task: 3 ... started
Requesting task: 3 ... finished
All requests are finished!
let queue = DispatchQueue(label: "requestTaskSerial", qos: .background, attributes: .concurrent)
for i in 1...3 {
group.enter()
queue.async(flags: .barrier){ [weak self] in
self?.request(task: i, time: Double(3-i), completion: {
group.leave()
})
}
}
group.notify(queue: .main) { [weak self] in
print("All requests are finished!")
self?.view.backgroundColor = .systemGreen
}
輸出:異步任務順序執行
Requesting task: 3 ... started
Requesting task: 3 ... finished
Requesting task: 2 ... started
Requesting task: 1 ... started
Requesting task: 2 ... finished
Requesting task: 1 ... finished
All requests are finished!
Operation & OperationQueue
Operation
自定義
同步
operation只需要重寫main()
方法,然后當main方法體執行完后,就isFinished
,一般用于同步操作(如數據讀取)
自定義
異步
operation時,需要手動維護isFinished
、isExecuting
等狀態,所以較為復雜
class RequestOperation: Operation {
var task: Int
var time: TimeInterval
init (task: Int, time: TimeInterval, completion: @escaping ()->Void) {
self.task = task
self.time = time
super.init()
self.completionBlock = completion
}
private var _finished: Bool = false {
willSet{
willChangeValue(forKey: "isFinished")
}
didSet{
didChangeValue(forKey: "isFinished")
}
}
private var _executing: Bool = false {
willSet{
willChangeValue(forKey: "isExecuting")
}
didSet{
didChangeValue(forKey: "isExecuting")
}
}
override var isFinished: Bool {
return _finished
}
override var isExecuting: Bool {
return _executing
}
override var isAsynchronous: Bool {
return true
}
private func done() {
super.cancel()
_executing = false
_finished = true
completionBlock?()
}
// 注意加鎖,避免多次調用
override func cancel() {
objc_sync_enter(self)
done()
objc_sync_exit(self)
}
override func start() {
guard !isCancelled else {
done()
return
}
_executing = true
request(task: task, time: time) { [weak self] in
self?.done()
}
}
}
使用:
func request(task: Int, time: TimeInterval, completion: (()->Void)?) {
DispatchQueue.global().async {
print("Requesting task: \(task) ... started")
Thread.sleep(forTimeInterval: time)
print("Requesting task: \(task) ... finished")
DispatchQueue.main.async(execute: completion ?? {})
}
}
@IBAction func startOneByOneRequesting(_ sender: UIControl) {
let queue = OperationQueue()
var previousOperation: Operation?
for i in 1...3 {
let op = RequestOperation(task: i, time: Double(3-i), completion: {
print("Operation \(i) is completed!")
if i == 3 {
print("All tasks are done !!!")
}
})
if let previous = previousOperation {
op.addDependency(previous)
}
queue.addOperation(op)
previousOperation = op
}
}
輸出:
Requesting task: 1 ... started
Requesting task: 1 ... finished
Operation 1 is completed!
Requesting task: 2 ... started
Requesting task: 2 ... finished
Operation 2 is completed!
Requesting task: 3 ... started
Requesting task: 3 ... finished
Operation 3 is completed!
All tasks are done !!!
OperationQueue
-
最大并發操作數:
maxConcurrentOperationCount
-
maxConcurrentOperationCount
默認情況下為-1,表示不進行限制,可進行并發執行。 -
maxConcurrentOperationCount
為1時,隊列為串行隊列。只能串行執行。
-
queuePriority
屬性決定了進入準備就緒狀態下的操作之間的開始執行順序。并且,優先級不能取代依賴關系。線程間的通信
在 iOS 開發過程中,我們一般在主線程里邊進行 UI 刷新,例如:點擊、滾動、拖拽等事件。我們通常把一些耗時的操作放在其他線程,比如說圖片下載、文件上傳等耗時操作。而當我們有時候在其他線程完成了耗時操作時,需要回到主線程,那么就用到了線程之間的通訊。
-
線程同步:
- 可理解為線程 A 和 線程 B 一塊配合,A 執行到一定程度時要依靠線程 B 的某個結果,于是停下來,示意 B 運行;B 依言執行,再將結果給 A;A 再繼續操作。
- 若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作(更改變量),一般都需要考慮線程同步,否則的話就可能影響線程安全。
線程安全:
如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。
- 線程安全解決方案:
- 可以給線程加鎖,在一個線程執行該操作的時候,不允許其他線程進行操作。iOS 實現線程加鎖有很多種方式。@synchronized、 NSLock、NSRecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock、atomic(property) set/ge等等各種方式。這里我們使用 NSLock 對象來解決線程同步問題。NSLock 對象可以通過進入鎖時調用 lock 方法,解鎖時調用 unlock 方法來保證線程安全。
var ticketSurplusCount = 50
override func viewDidLoad() {
super.viewDidLoad()
///1.1 創建代表北京火車票售賣窗口
let operationForBeiJing = OperationQueue()
operationForBeiJing.maxConcurrentOperationCount = 1;
///1.2 創建賣票操作 op1
let op1 = BlockOperation{
self.saleTicketSafe()
}
///1.3 添加操作
operationForBeiJing.addOperation(op1)
///2.1創建代表上海火車票售賣窗口
let operationForShangHai = OperationQueue()
operationForShangHai.maxConcurrentOperationCount = 1;
///2.2創建賣票操作 op2
let op2 = BlockOperation{
self.saleTicketSafe()
}
///2.3 添加操作
operationForShangHai.addOperation(op2)
}
private func saleTicketSafe(){
while true {
objc_sync_enter(self)
if self.ticketSurplusCount > 0 {
self.ticketSurplusCount-=1;
print("剩余票數:\(self.ticketSurplusCount) 窗口:\(Thread.current)")
sleep(2)
}
objc_sync_exit(self)
if self.ticketSurplusCount <= 0 {
print("所有火車票均已售完")
break
}
}
}
迭代任務
如果一個任務可以分解為多個相似但獨立的子任務,那么迭代任務是提高性能最適合的選擇。
let queue = DispatchQueue.global() // 全局并發隊列
queue.async {
DispatchQueue.concurrentPerform(iterations: 100) {(index) -> Void in
// do something
}
//可以轉至主線程執行其他任務
DispatchQueue.main.async {
// do something
}
}
- 示例
var divideds = [Int]()
lazy var nums = Array(1...100000)
@IBAction func syncConcurrent(_ sender: UIControl) {
let queue = DispatchQueue.global(qos: .userInitiated)
func isNumber(_ value: Int, canBeDividedBy divisor: Int) -> Bool {
guard divisor != 0 else {
fatalError("Divisor can'not be zero!")
}
return value % divisor == 0
}
queue.async {
print("Started \(Date().timeIntervalSince1970)")
DispatchQueue.concurrentPerform(iterations: self.nums.count) { index in
let number = self.nums[index]
if isNumber(number, canBeDividedBy: 13) {
// print("Current Thread: \(Thread.current)")
DispatchQueue.main.async {
self.divideds.append(number)
}
}
}
print("Ended \(Date().timeIntervalSince1970)")
DispatchQueue.main.async {
print(self.divideds.map{ String($0) }.joined(separator: ", "))
}
}
}
1kw數計算耗時
Start 1648028143.466887
Ended 1648028153.098256
DispatchSource
GCD 中提供了一個
DispatchSource
類,它可以幫你監聽系統底層一些對象的活動,例如這些對象:Mach port、Unix descriptor、Unix signal、VFS node
,并允許你在這些活動發生時,向隊列提交一個任務以進行異步處理。
- 示例:
監聽指定目錄下文件變化(增、刪)
class DispatchSourceTest {
var filePath: String
var counter = 0
let queue = DispatchQueue.global()
init() {
filePath = "\(NSTemporaryDirectory())"
startObserve {
print("File was changed")
}
}
func startObserve(closure: @escaping () -> Void) {
let fileURL = URL(fileURLWithPath: filePath)
let monitoredDirectoryFileDescriptor = open(fileURL.path, O_EVTONLY)
let source = DispatchSource.makeFileSystemObjectSource(
fileDescriptor: monitoredDirectoryFileDescriptor,
eventMask: .write, queue: queue)
source.setEventHandler(handler: closure)
source.setCancelHandler {
close(monitoredDirectoryFileDescriptor)
}
source.resume()
}
func changeFile() {
DispatchSourceTest.createFile(name: "DispatchSourceTest.md", filePath: NSTemporaryDirectory())
counter += 1
let text = "\(counter)"
try! text.write(toFile: "\(filePath)/DispatchSourceTest.md", atomically: true, encoding: String.Encoding.utf8)
print("file writed.")
}
static func createFile(name: String, filePath: String){
let manager = FileManager.default
let fileBaseUrl = URL(fileURLWithPath: filePath)
let file = fileBaseUrl.appendingPathComponent(name)
print("文件: \(file)")
// 寫入 "hello world"
let exist = manager.fileExists(atPath: file.path)
if !exist {
let data = Data(base64Encoded:"aGVsbG8gd29ybGQ=" ,options:.ignoreUnknownCharacters)
let createSuccess = manager.createFile(atPath: file.path,contents:data,attributes:nil)
print("文件創建結果: \(createSuccess)")
}
}
}
DispatchIO
DispatchIO 對象提供一個操作文件描述符的通道。簡單講你可以利用多線程異步高效地讀寫文件。
發起讀寫操作一般步驟如下:
- 創建 DispatchIO 對象,或者說創建一個通道,并設置結束處理閉包。
- 調用 read / write 方法
- 調用 close 方法關閉通道
- 在 close 方法后系統將自動調用結束處理閉包
let filePath: NSString = "test.zip"
// 創建一個可讀寫的文件描述符
let fileDescriptor = open(filePath.utf8String!, (O_RDWR | O_CREAT | O_APPEND), (S_IRWXU | S_IRWXG))
let queue = DispatchQueue(label: "com.sinkingsoul.DispatchQueueTest.serialQueue")
let cleanupHandler: (Int32) -> Void = { errorNumber in
}
let io = DispatchIO(type: .stream, fileDescriptor: fileDescriptor, queue: queue, cleanupHandler: cleanupHandler)
- 設置閾值
io.setLimit(highWater: 1024*1024)
io.setLimit(lowWater: 1024*1024)
- 讀操作
io.read(offset: 0, length: Int.max, queue: ioReadQueue)
{ doneReading, data, error in
if (error > 0) {
print("讀取發生錯誤了,錯誤碼:\(error)")
return
}
if (data != nil) {
// 使用數據
}
if (doneReading) {
ioRead.close()
}
}
- 寫操作
io.write(offset: 0, data: data!, queue: ioWriteQueue)
{ doneWriting, data, error in
if (error > 0) {
print("寫入發生錯誤了,錯誤碼:\(error)")
return
}
if doneWriting {
//...
ioWrite.close()
}
}
- 示例:
大文件合并
class DispatchIOTest {
/// 利用很小的內存空間及同一隊列讀寫方式合并文件
static func combineFileWithOneQueue() {
let files: NSArray = ["/Users/xxx/Downloads/gcd.mp4.zip.001",
"/Users/xxx/Downloads/gcd.mp4.zip.002"]
let outFile: NSString = "/Users/xxx/Downloads/gcd.mp4.zip"
let ioQueue = DispatchQueue(
label: "com.sinkingsoul.DispatchQueueTest.serialQueue")
let queueGroup = DispatchGroup()
let ioWriteCleanupHandler: (Int32) -> Void = { errorNumber in
print("寫入文件完成 @\(Date())。")
}
let ioReadCleanupHandler: (Int32) -> Void = { errorNumber in
print("讀取文件完成。")
}
let ioWrite = DispatchIO(type: .stream,
path: outFile.utf8String!,
oflag: (O_RDWR | O_CREAT | O_APPEND),
mode: (S_IRWXU | S_IRWXG),
queue: ioQueue,
cleanupHandler: ioWriteCleanupHandler)
ioWrite?.setLimit(highWater: 1024*1024)
// print("開始操作 @\(Date()).")
files.enumerateObjects { fileName, index, stop in
if stop.pointee.boolValue {
return
}
queueGroup.enter()
let ioRead = DispatchIO(type: .stream,
path: (fileName as! NSString).utf8String!,
oflag: O_RDONLY,
mode: 0,
queue: ioQueue,
cleanupHandler: ioReadCleanupHandler)
ioRead?.setLimit(highWater: 1024*1024)
print("開始讀取文件: \(fileName) 的數據")
ioRead?.read(offset: 0, length: Int.max, queue: ioQueue) { doneReading, data, error in
print("當前讀線程:\(Thread.current)--->")
if (error > 0 || stop.pointee.boolValue) {
print("讀取發生錯誤了,錯誤碼:\(error)")
ioWrite?.close()
stop.pointee = true
return
}
if (data != nil) {
let bytesRead: size_t = data!.count
if (bytesRead > 0) {
queueGroup.enter()
ioWrite?.write(offset: 0, data: data!, queue: ioQueue) {
doneWriting, data, error in
print("當前寫線程:\(Thread.current)--->")
if (error > 0 || stop.pointee.boolValue) {
print("寫入發生錯誤了,錯誤碼:\(error)")
ioRead?.close()
stop.pointee = true
queueGroup.leave()
return
}
if doneWriting {
queueGroup.leave()
}
print("--->當前寫線程:\(Thread.current)")
}
}
}
if (doneReading) {
ioRead?.close()
if (files.count == (index+1)) {
ioWrite?.close()
}
queueGroup.leave()
}
print("--->當前讀線程:\(Thread.current)")
}
_ = queueGroup.wait(timeout: .distantFuture)
}
}
}
可以發現在讀寫過程中額外占用了 3M 左右內存,用時 2s 左右。這個結果中,內存占用比單隊列大(這個比較好理解),但速度還更慢了,性能瓶頸很有可能是在磁盤讀寫上。所以涉及文件寫操作時,并不是線程越多越快,要考慮傳輸速度、文件大小等因素。
DispatchData
DispatchData 對象可以管理基于內存的數據緩沖區。這個數據緩沖區對外表現為連續的內存區域,但內部可能由多個獨立的內存區域組成。
DispatchData 對象很多特性類似于 Data 對象,且 Data 對象可以轉換為 DispatchData 對象,而通過 DispatchIO 的 read 方法獲得的數據也是封裝為 DispatchData 對象的。
首先將兩個文件轉換為 Data 對象,再轉換為 DispatchData 對象,然后拼接兩個對象為一個 DispatchData 對象,最后通過 DispatchIO 的 write 方法寫入文件中。看起來有多次的轉換過程,實際上 Data 類型讀取文件時支持虛擬隱射的方式,而 DispatchData 類型更是支持多個數據塊虛擬拼接,也不占用什么內存。
示例:
多文件合并
/// 利用 DispatchData 類型快速合并文件
static func combineFileWithDispatchData() {
let filePathArray = ["/Users/xxx/Downloads/gcd.mp4.zip.001",
"/Users/xxx/Downloads/gcd.mp4.zip.002"]
let outputFilePath: NSString = "/Users/xxx/Downloads/gcd.mp4.zip"
let ioWriteQueue = DispatchQueue(
label: "com.sinkingsoul.DispatchQueueTest.serialQueue")
let ioWriteCleanupHandler: (Int32) -> Void = { errorNumber in
print("寫入文件完成 @\(Date()).")
}
let ioWrite = DispatchIO(type: .stream,
path: outputFilePath.utf8String!,
oflag: (O_RDWR | O_CREAT | O_APPEND),
mode: (S_IRWXU | S_IRWXG),
queue: ioWriteQueue,
cleanupHandler: ioWriteCleanupHandler)
ioWrite?.setLimit(highWater: 1024*1024*2)
print("開始操作 @\(Date()).")
// 將所有文件合并為一個 DispatchData 對象
let dispatchData = filePathArray.reduce(DispatchData.empty) { data, filePath in
// 將文件轉換為 Data
let url = URL(fileURLWithPath: filePath)
let fileData = try! Data(contentsOf: url, options: .mappedIfSafe)
var tempData = data
// 將 Data 轉換為 DispatchData
let dispatchData = fileData.withUnsafeBytes {
(u8Ptr: UnsafePointer<UInt8>) -> DispatchData in
let rawPtr = UnsafeRawPointer(u8Ptr)
let innerData = Unmanaged.passRetained(fileData as NSData)
return DispatchData(bytesNoCopy:
UnsafeRawBufferPointer(start: rawPtr, count: fileData.count),
deallocator: .custom(nil, innerData.release))
}
// 拼接 DispatchData
tempData.append(dispatchData)
return tempData
}
//將 DispatchData 對象寫入結果文件中
ioWrite?.write(offset: 0, data: dispatchData, queue: ioWriteQueue) {
doneWriting, data, error in
if (error > 0) {
print("寫入發生錯誤了,錯誤碼:\(error)")
return
}
if data != nil {
// print("正在寫入文件,剩余大小:\(data!.count) bytes.")
}
if (doneWriting) {
ioWrite?.close()
}
}
}
可以發現在整個讀寫過程中幾乎沒有額外占用內存,速度很快在 1s 左右,這個讀寫方案堪稱完美,這要歸功于 DispatchData 的虛擬拼接和 DispatchIO 的分塊讀寫大小控制。這里順便提一下 DispatchIO 數據閥值上限 highWater,經過測試,如果設置為 1M,將耗時 4s 左右,設為 2M 及以上時,耗時均為 1s 左右,非常快速,而所有閥值的內存占用都很少。所以設置合理的閥值,對性能的改善也是有幫助的。
DispatchWorkItem
let workItem = DispatchWorkItem(qos: .default, flags: DispatchWorkItemFlags()) {
// Do something
}
let queue = DispatchQueue.global()
queue.async(execute: workItem)
// 或
workItem.perform()
workItem.wait()
workItem.wait(timeout: DispatchTime) // 指定等待時間
workItem.wait(wallTimeout: DispatchWallTime) // 指定等待時間
- 示例
func execute(work: @escaping ()->Void, completion: @escaping ()->Void) {
let item = DispatchWorkItem(block: work)
DispatchQueue.global().async(execute: item)
item.wait()
completion()
}
execute {
print("[\(Date().timeIntervalSince1970)] I start sleeping and will finish after 5s...")
Thread.sleep(forTimeInterval: 5)
} completion: {
print("[\(Date().timeIntervalSince1970)] I'm done!")
}
/*
[1648089354.896827] I start sleeping and will finish after 5s...
[1648089359.901157] I'm done!
*/
參考:https://www.cnblogs.com/lxlx1798/articles/15040615.html
http://www.lxweimin.com/p/52457e86cfe0