I realized that there is a difference between using DispatchQueue.main.asyncAfter(deadline: .now()) and perform(_:with:afterDelay:0) when main queue is "busy".
Note that perform(_:with:afterDelay:) is called from main queue in my situation.
Seem like DispatchQueue.main.asyncAfter(deadline: .now()) performs the task immediately at the next run loop without caring about main queue but perform(_:with:afterDelay:) with 0 delay will wait and perform the task only when main queue is "free" (maybe it won't be called at the next run loop).
According to Apple document for perform(_:with:afterDelay:)
Specifying a delay of 0 does not necessarily cause the selector to be performed immediately. The selector is still queued on the thread’s run loop and performed as soon as possible.
I'm not sure that I understand them right so could anyone help me to explain exactly what is the difference under the hood between them? What does performed as soon as possible mean?
I found a same question here but seem like it's not what I want.
I created this standalone test to explore this topic.
class ViewController: UIViewController {
    @objc func test1(_ info: Any) {
        guard let str = info as? String else { return }
        sleep(1)
        print(str)
        if str != "selector 3" {
            self.perform(#selector(test1), with: "selector 3", afterDelay: 0)
        }
        DispatchQueue.main.asyncAfter(deadline: .now()) {
            sleep(1)
            print("dispatch 4 queued by \(str)")
        }
    }
    @IBAction func test(_ sender: UIButton) {
        print("begin test")
        self.perform(#selector(test1), with: "selector 1", afterDelay: 0)
        DispatchQueue.main.asyncAfter(deadline: .now()) {
            DispatchQueue.main.asyncAfter(deadline: .now()) {
                sleep(1)
                print("dispatch 3")
            }
            sleep(1)
            print("dispatch 1")
        }
        self.perform(#selector(test1), with: "selector 2", afterDelay: 0)
        DispatchQueue.main.asyncAfter(deadline: .now()) {
            sleep(1)
            print("dispatch 2")
        }
        print("end test")
    }
}
Resulting output:
begin test
end test
dispatch 1
dispatch 2
selector 1
selector 2
dispatch 3
dispatch 4 queued by selector 1
dispatch 4 queued by selector 2
selector 3
selector 3
dispatch 4 queued by selector 3
dispatch 4 queued by selector 3
Things to observe:
begin test and end test print before any other output showing that both perform(_:with:afterDelay:) and DispatchQueue.main.asyncAfter are queued and run later.DispatchQueues run before the performs even though they are queued in a different order.dispatch 3 doesn't jump ahead of selector 1 and selector 2 even though it is queued before dispatch 1 prints.Conclusions:
Dispatch.main.asyncAfter and perform(_:with:afterDelay:) queue their selector/closure to be performed later.  Since you are running perform(_:with:afterDelay:) on the main thread, it uses the main queue for its scheduling.Dispatch.main.asyncAfter(0) calls are queued before perform(_:with:afterDelay:0) calls when queued in the same run loop.  Note: if any delay at all is added to the Dispatch.main.asyncAfter, it will then be queued after the perform(_:with:afterDelay:).  Try using .now() + .milliseconds(1), for example.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With