問題來源
首先來看一段程式碼,然後猜測輸出結果,注意輸出內容的順序:
class Dog: NSObject {
@objc
func getDogName() {
debugPrint("ハチ公")
}
}
var runloop: CFRunLoop!
let sem = DispatchSemaphore(value: 0)
let thread = Thread {
RunLoop.current.add(NSMachPort(), forMode: .common)
runloop = CFRunLoopGetCurrent()
sem.signal()
CFRunLoopRun()
}
thread.start()
sem.wait()
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
CFRunLoopPerformBlock(runloop, CFRunLoopMode.defaultMode.rawValue) {
debugPrint("2")
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
debugPrint("1")
let dog = Dog()
dog.perform(#selector(dog.getDogName), with: nil)
})
}
RunLoop.current.run()
複製程式碼
輸出結果為1
、ハチ公
。
原因分析
為什麼沒有輸出2
我們其實已經將block
任務加到了thread
對應的runloop
中了,但是此時的runloop
已經處於休眠狀態,一直沒有被喚醒,所以block
內任務一直得不到執行。
如何執行到block
內方法
要想執行到block
內方法我們需要喚醒runloop
,可以使用
let dog = Dog()
dog.perform(#selector(dog.getDogName), with: nil)
CFRunLoopWakeUp(runloop)
複製程式碼
這樣runloop
被喚醒,block
方法得到了執行,輸出結果為1
、ハチ公
、2
。
新增timer
的方式
喚醒runloop
的方式有多種,有一張圖描述的很不錯:
timer
的方式,喚醒runloop
:
let dog = Dog()
dog.perform(#selector(dog.getDogName), on: thread, with: nil, waitUntilDone: false)
複製程式碼
這個方法會在指定執行緒新增一個定時器,然後執行相應方法。這樣通過新增定時器方法成功喚醒runloop
,又因為block
新增到任務佇列較早所以輸出順序是1
、2
、ハチ公
。
關於perform
的幾個方法
public func perform(_ aSelector: Selector!) -> Unmanaged<AnyObject>!
複製程式碼
這個方法是NSObjectProtocol
內的方法,會在當前執行緒通過訊息傳送機制呼叫方法。
open func perform(_ aSelector: Selector, on thr: Thread, with arg: Any?, waitUntilDone wait: Bool)
複製程式碼
這個方法是NSObject
在Thread
檔案下的一個extension
,他可以指定執行緒並且新增一個timer
去執行這個任務,這就是為什麼可以喚醒runloop
的原因。waitUntilDone
這個引數可以設定是否等到selector
方法執行完畢後,執行後面的程式碼。
open func perform(_ aSelector: Selector, with anArgument: Any?, afterDelay delay: TimeInterval)
複製程式碼
這個方法是NSObject
在Runloop
檔案下的一個extension
。與上一個方法類似,只不過預設執行在當前執行緒。並且可以設定delay
時間。
修改runloop
如果我們將程式碼改為:
let thread = Thread {
// RunLoop.current.add(NSMachPort(), forMode: .common)
runloop = CFRunLoopGetCurrent()
sem.signal()
CFRunLoopRun()
}
let dog = Dog()
dog.perform(#selector(dog.getDogName), on: thread, with: nil, waitUntilDone: false)
複製程式碼
這樣只會列印1
。因為雖然我們獲取到了當前執行緒的runloop
,但是由於runloop
沒有新增timer
或source
,所以會馬上釋放掉,這樣執行緒也被釋放掉了,所以再去操作這個執行緒就得不到任何結果了。