Runloop & 方法調用

問題來源

首先來看一段代碼,而後猜想輸出結果,注意輸出內容的順序:bash

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ハチ公async

緣由分析

爲何沒有輸出2

咱們其實已經將block任務加到了thread對應的runloop中了,可是此時的runloop已經處於休眠狀態,一直沒有被喚醒,因此block內任務一直得不到執行。oop

如何執行到block內方法

要想執行到block內方法咱們須要喚醒runloop,可使用ui

let dog = Dog()
dog.perform(#selector(dog.getDogName), with: nil)
CFRunLoopWakeUp(runloop)
複製代碼

這樣runloop被喚醒,block方法獲得了執行,輸出結果爲1ハチ公2spa

添加timer的方式

喚醒runloop的方式有多種,有一張圖描述的很不錯:線程

從這張圖中咱們不難看出能夠經過添加 timer的方式,喚醒 runloop:

let dog = Dog()
dog.perform(#selector(dog.getDogName), on: thread, with: nil, waitUntilDone: false)
複製代碼

這個方法會在指定線程添加一個定時器,而後執行相應方法。這樣經過添加定時器方法成功喚醒runloop,又由於block添加到任務隊列較早因此輸出順序是12ハチ公debug

關於perform的幾個方法

public func perform(_ aSelector: Selector!) -> Unmanaged<AnyObject>!
複製代碼

這個方法是NSObjectProtocol內的方法,會在當前線程經過消息發送機制調用方法。code

open func perform(_ aSelector: Selector, on thr: Thread, with arg: Any?, waitUntilDone wait: Bool)
複製代碼

這個方法是NSObjectThread文件下的一個extension,他能夠指定線程而且添加一個timer去執行這個任務,這就是爲何能夠喚醒runloop的緣由。waitUntilDone這個參數能夠設置是否等到selector方法執行完畢後,執行後面的代碼。orm

open func perform(_ aSelector: Selector, with anArgument: Any?, afterDelay delay: TimeInterval)
複製代碼

這個方法是NSObjectRunloop文件下的一個extension。與上一個方法相似,只不過默認執行在當前線程。而且能夠設定delay時間。cdn

修改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沒有添加timersource,因此會立刻釋放掉,這樣線程也被釋放掉了,因此再去操做這個線程就得不到任何結果了。

問題來源

相關文章
相關標籤/搜索