How to Dispatch Functions in Swift the Right Way

How do I dispatch functions in Swift the right way?

Let's say you had some function like so:

func calculate(foo: String, bar: Int) -> Int {
// slow calculations performed here

return result
}

If you wanted to do that asynchronously, you could wrap it in something like this:

func calculate(foo: String, bar: Int, completionHandler: @escaping (Int) -> Void) {
DispatchQueue.global().async {
// slow calculations performed here

completionHandler(result)
}
}

Or, alternatively, if you want to ensure the completion handler is always called on the main queue, you could have this do that for you, too:

func calculate(foo: String, bar: Int, completionHandler: @escaping (Int) -> Void) {
DispatchQueue.global().async {
// slow calculations performed here

DispatchQueue.main.async {
completionHandler(result)
}
}
}

For the work being performed in the background, you may use a different priority background queue, or your might use your own custom queue or your own operation queue. But those details aren't really material to the question at hand.

What is relevant is that this function, itself, doesn't return any value even though the underlying synchronous function does. Instead, this asynchronous rendition is passing the value back via the completionHandler closure. Thus, you would use it like so:

calculate(foo: "life", bar: 42) { result in
// we can use the `result` here (e.g. update model or UI accordingly)

print("the result is = \(result)")
}

// but don't try to use `result` here, because we get here immediately, before
// the above slow, asynchronous process is done

(FYI, all of the above examples are Swift 3. For Swift 2.3 rendition, see previous version of this answer.)

How do I execute code once and only once in Swift?

Static properties initialized by a closure are run lazily and at most once, so this prints only once, in spite of being called twice:

/*
run like:

swift once.swift
swift once.swift run

to see both cases
*/
class Once {
static let run: Void = {
print("Behold! \(__FUNCTION__) runs!")
return ()
}()
}

if Process.arguments.indexOf("run") != nil {
let _ = Once.run
let _ = Once.run
print("Called twice, but only printed \"Behold\" once, as desired.")
} else {
print("Note how it's run lazily, so you won't see the \"Behold\" text now.")
}

Example runs:

~/W/WhenDoesStaticDefaultRun> swift once.swift
Note how it's run lazily, so you won't see the "Behold" text now.
~/W/WhenDoesStaticDefaultRun> swift once.swift run
Behold! Once runs!
Called twice, but only printed "Behold" once, as desired.

Swift which method dispatch is used?

This method can be statically dispatched, because aaaa has one fixed, statically-known type (AAA).

If you instead had something like anyBBBB: any BBBB = aaa, then calling anyBBBB.printSome() would need dynamic dispatch, because it's not known which concrete type is involved until runtime. (Of course, if the compiler's data-flow analysis can prove there's only one possible type in that spot, then it can switch back to static dispatch. This is called Devirtualization)

Proper way to do polling in swift?

You have 2 options:

  • Use NSTimer
  • Use a DispatchSourceTimer

Using NSTimer is pretty easy, but it needs an active run loop, so if you need to poll on a background thread things could be a little bit tricky, because you will need to create a thread and keep alive a run loop on it (probably the timer itself will keep the run loop alive).

DispatchSourceTimer on the other hand works using queues. You can easily create a dispatch source timer from one of the system provided queues or create one.

    var timer: DispatchSourceTimer?
let queue = DispatchQueue.global(qos: .background)
guard let timer = DispatchSource.makeTimerSource(queue: queue) else { return }
timer.scheduleRepeating(deadline: .now(), interval: .seconds(100), leeway: .seconds(1))
timer.setEventHandler(handler: {
// Your code
})
timer.resume()

The leeway arguments is the amount of time that the system can defer the timer.

In Swift how to call method with parameters on GCD main thread?

Modern versions of Swift use DispatchQueue.main.async to dispatch to the main thread:

DispatchQueue.main.async { 
// your code here
}

To dispatch after on the main queue, use:

DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
// your code here
}

Older versions of Swift used:

dispatch_async(dispatch_get_main_queue(), {
let delegateObj = UIApplication.sharedApplication().delegate as YourAppDelegateClass
delegateObj.addUIImage("yourstring")
})

Is there a elegant way to confine a class to a thread?

Approach:

Use a property wrapper

Code:

@propertyWrapper
struct QueueVariable<T> {

private var _value : T
private let queue : DispatchQueue

var wrappedValue : T {

get {
queue.sync { _value }
}

set {
queue.sync { _value = newValue }
}
}

init(value: T,
queue: DispatchQueue = DispatchQueue(label: "sync queue")) {
_value = value
self.queue = queue
}
}

class Test {

@QueueVariable(value: 100, queue: DispatchQueue(label: "aaa"))
var a1

@QueueVariable(value: "aaa")
var a2
}

Invoking

Either pass the dispatch queue, or a new one would be generated.

let t1 = Test()

t1.a1 += 5

print(t1.a1)
print(t1.a2)

How do I write dispatch_after GCD in Swift 3, 4, and 5?

The syntax is simply:

// to run something in 0.1 seconds

DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
// your code here
}

Note, the above syntax of adding seconds as a Double seems to be a source of confusion (esp since we were accustomed to adding nsec). That "add seconds as Double" syntax works because deadline is a DispatchTime and, behind the scenes, there is a + operator that will take a Double and add that many seconds to the DispatchTime:

public func +(time: DispatchTime, seconds: Double) -> DispatchTime

But, if you really want to add an integer number of msec, μs, or nsec to the DispatchTime, you can also add a DispatchTimeInterval to a DispatchTime. That means you can do:

DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
os_log("500 msec seconds later")
}

DispatchQueue.main.asyncAfter(deadline: .now() + .microseconds(1_000_000)) {
os_log("1m μs seconds later")
}

DispatchQueue.main.asyncAfter(deadline: .now() + .nanoseconds(1_500_000_000)) {
os_log("1.5b nsec seconds later")
}

These all seamlessly work because of this separate overload method for the + operator in the DispatchTime class.

public func +(time: DispatchTime, interval: DispatchTimeInterval) -> DispatchTime

It was asked how one goes about canceling a dispatched task. To do this, use DispatchWorkItem. For example, this starts a task that will fire in five seconds, or if the view controller is dismissed and deallocated, its deinit will cancel the task:

class ViewController: UIViewController {

private var item: DispatchWorkItem?

override func viewDidLoad() {
super.viewDidLoad()

item = DispatchWorkItem { [weak self] in
self?.doSomething()
self?.item = nil
}

DispatchQueue.main.asyncAfter(deadline: .now() + 5, execute: item!)
}

deinit {
item?.cancel()
}

func doSomething() { ... }

}

Note the use of the [weak self] capture list in the DispatchWorkItem. This is essential to avoid a strong reference cycle. Also note that this does not do a preemptive cancelation, but rather just stops the task from starting if it hasn’t already. But if it has already started by the time it encounters the cancel() call, the block will finish its execution (unless you’re manually checking isCancelled inside the block).

Protocol extension method dispatch in Swift 2.0

Smells like a bug.

The only workaround I came up with was very ugly...

protocol E {
func test()
}

func E_test(_s: E) {
print("jello")
}

extension E {
func test() { E_test(self) }
}

class A: E {
func test() { E_test(self) }
}

class B: A {
override func test() {
print("hello")
}
}

let b: A = B()
b.test()


Related Topics



Leave a reply



Submit