Blog

Swift Tip: Atomic Variables

In our Swift Talk backend, we load static data when our app launches. All episodes and collections are loaded from a git repository, before being transformed and made ready to use. When the repository changes we receive a webhook, which we use to reload the data.

If we declared our episodes and collections as regular properties, we might run into a race condition: we could be simultaneously reading a property on one thread while writing to it on another. Like many threading bugs, this is something that will almost never happen, but when it does it is hard to track down, and can crash our app.

To remedy this, we use an Atomic class that uses GCD queues to provide synchronized access to a variable.

final class Atomic<A> {
    private let queue = DispatchQueue(label: "Atomic serial queue")
    private var _value: A
    init(_ value: A) {
        self._value = value
    }

    var value: A {
        return queue.sync { self._value }
    }
}

Of course, just being able to read a value is uninteresting, we need a way modify it as well. It is very tempting to change value to be get set, like this:

var value: A {
    get {
        return queue.sync { self._value }
    }
    set { // BAD IDEA
        queue.sync {
            self._value = newValue
        }
    }
}

However, this is a very bad idea. Let’s look at the following example to see why:

var x = Atomic<Int>(5)
x.value += 1

While it looks like the increment is an atomic operation, it isn’t. First, the value is fetched (atomically), then it’s incremented, then it’s written back (atomically). Although the atomic read and write accesses can no longer crash our app, we can end up with the wrong value in the value property if another thread modifies it in between read and write.

In Swift 5, we might be able to solve this using _modify (also see Ben Cohen‘s Functional Swift talk, ’Fast Safe Mutable State‘). However, until that’s possible we need a different solution.

Instead, we add a mutate method that atomically mutates the underlying variable:

final class Atomic<A> {
    private let queue = DispatchQueue(label: "Atomic serial queue")
    private var _value: A
    init(_ value: A) {
        self._value = value
    }

    var value: A {
        get {
            return queue.sync { self._value }
        }
    }

    func mutate(_ transform: (inout A) -> ()) {
        queue.sync {
            transform(&self._value)
        }
    }
}

This makes the call site a little more verbose, but that’s a small price to pay for thread-safety:

let x = Atomic<Int>(5)
x.mutate { $0 += 1 }

On Swift Talk, we have covered a number of topics around server-side Swift programming, which you can find in our Server-Side Swift Collection.

In Swift Talk Episode 42, we implement thread safety for a type in a reactive framework; a specific case that helps us learn generic solutions that can be used in many other places.

If you’d like to support us, you can subscribe, or give a subscription as a gift. 🎁


Stay up-to-date with our newsletter or follow us on Twitter.

Back to the Blog

recent posts