26

Suppose we have the following code defining a continuous loop (as in for a game):

let queue = DispatchQueue(label: "DemoSerialQueue")
let workItem = DispatchWorkItem{ print("Hello World") }
func gameLoop() {
  queue.async(execute:workItem)
}

Would the above code be more efficient in terms of speed than the following:

func gameLoop() {
  queue.async{ print("Hello World") }
}

In particular I'm wondering if the second form would be allocating a closure on every loop and thus incuring a performance hit.

gloo
  • 2,490
  • 3
  • 22
  • 38
  • There’s no real benefit to using `DispatchWorkItem` as above. It’s most useful when you need to reference that object associated with that task later (e.g. checking to see if task has canceled, i.e. https://stackoverflow.com/a/38372384/1271826). (That having been said, when you get to sufficient complexity that you want to consider encapsulating blocks of work, then you might also consider [`Operation`](https://developer.apple.com/documentation/foundation/operation) and [`OperationQueue`](https://developer.apple.com/documentation/foundation/operationqueue).) – Rob Jan 06 '19 at 17:03
  • 1
    By the way, if you want to see other advantages of dispatch work items, see 14:20 into [Concurrent Programming with GCD](https://developer.apple.com/videos/play/wwdc2016/720/?time=860) WWDC 2016 video. – Rob Jan 06 '19 at 17:34
  • So does the second form not allocate a closure on every run of the loop, whereas the first would bypass this? – gloo Jan 08 '19 at 09:06
  • Yes, that is the case, though the difference is generally not observable in practical applications... – Rob Jan 08 '19 at 10:52

1 Answers1

53

The DispatchWorkItem class is an encapsulation of the concept of work item. There are few benefits.

A dispatch work item has a cancel flag. If it is cancelled before running, the dispatch queue won’t execute it and will skip it. If it is cancelled during its execution, the cancel property return True. In that case, we can abort the execution

By encapsulating our request code in a work item, we can very easily cancel it whenever it's replaced by a new one, like this:

class SearchViewController: UIViewController, UISearchBarDelegate {
    // We keep track of the pending work item as a property
    private var pendingRequestWorkItem: DispatchWorkItem?

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        // Cancel the currently pending item
        pendingRequestWorkItem?.cancel()

        // Wrap our request in a work item
        let requestWorkItem = DispatchWorkItem { [weak self] in
            self?.resultsLoader.loadResults(forQuery: searchText)
        }

        // Save the new work item and execute it after 250 ms
        pendingRequestWorkItem = requestWorkItem
        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(250),
                                      execute: requestWorkItem)
    }
}

In general, Dispatch functions can take a block or a DispatchWorkItem as a parameter. So there won't any performance hit as we use blocks in both cases. Use the one that suits you the most.

Dinesh Selvaraj
  • 634
  • 7
  • 9