Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IOS - Difference between DispatchQueue.main.asyncAfter(deadline: .now()) and perform(_:with:afterDelay:) with 0 delay

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.

like image 316
trungduc Avatar asked Sep 03 '25 04:09

trungduc


1 Answers

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:

  1. 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.
  2. The first two DispatchQueues run before the performs even though they are queued in a different order.
  3. All of the prints happen one second apart meaning they are all being run on the same queue waiting for the previous to finish.
  4. dispatch 3 doesn't jump ahead of selector 1 and selector 2 even though it is queued before dispatch 1 prints.

Conclusions:

  1. Both 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.
  2. For some unknown (to me) reason, 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.
  3. performed as soon as possible is just another way to say they are queued and processed in queue order. Depending on how long the tasks take in the queue ahead, it might take many runs of the run loop before a task is finally handled.
like image 98
vacawama Avatar answered Sep 04 '25 21:09

vacawama



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!