1

My Swift code uses objc_sync_enter & objc_sync_exit methods to implement @synchronized primitive (available in Objective-C) in Swift. However, this answer claims it is an outdated as well as inappropriate way to implement synchronised access in Swift. While the reasons for the same are not provided, I would like to know modern ways of implementing critical sections in Swift where a number of variables are accessed in a block, but the same variables are written infrequently (such as when UI orientation or app settings change).

10
  • 1
    Have you looked into any sort of locking mechanisms like NSLock, NSRecursiveLock, os_unfair_lock to do locking in Swift? There are resources already here on SO that should be able to help you out. (e.g., you can implement locking with os_unfair_lock like so as an alternative) Commented Oct 12, 2022 at 13:57
  • 1
    As for why you should avoid objc_sync_enter/objc_sync_exit, one main reason: it's really easy to get wrong, and lock on a struct, which isn't valid. See, e.g., stackoverflow.com/questions/70896707/…. (It's also much less performant than other locking mechanisms, but that's a concern in Obj-C too) Commented Oct 12, 2022 at 14:00
  • 1
    NSLock uses pthreads; from the docs: "The NSLock class uses POSIX threads to implement its locking behavior". os_unfair_lock is implemented entirely differently, but shares the same general ideas. In general, though, I wouldn't be concerned about the specific implementation details in either case: just know that these are modern tools that are safe to use from Swift. Commented Oct 12, 2022 at 16:02
  • 2
    My rule of thumb about locks recently is “if you have to ask, you shouldn’t use them”. Try the more structured synchronization/concurrency primitives first (Actors, Dispatch Queues), and only drop down to manual twiddling with locks if you have a very compelling reason Commented Oct 12, 2022 at 17:20
  • 1
    @ItaiFerber “NSLock ... os_unfair_lock ... in either case: just know that these are modern tools that are safe to use from Swift.” I know you know this, but one has be extremely careful with os_unfair_lock from Swift. This is why iOS 16 introduced OSAllocatedUnfairLock, to avoid the rigmarole required to use os_unfair_lock safely from Swift. Better to stick w actors or GCD, IMHO, unless performance is of paramount concern (which it generally isn’t). Commented Oct 16, 2022 at 21:18

1 Answer 1

0

You asked:

I would like to know modern ways of implementing critical sections in Swift where a number of variables are accessed in a block

The modern way is to use actors. See The Swift Programming Guide: Concurrency: Actors:

actor TemperatureLogger {
    let label: String
    var measurements: [Int]
    private(set) var max: Int

    init(label: String, measurement: Int) {
        self.label = label
        self.measurements = [measurement]
        self.max = measurement
    }

    ...

    func update(with measurement: Int) {
        measurements.append(measurement)
        if measurement > max {
            max = measurement
        }
    }
}

Also see WWDC 2022 video Eliminate data races using Swift Concurrency or 2021’s Protect mutable state with Swift actors.


In codebases where you have not yet adopted Swift concurrency (e.g., async-await) you could use a lock or GCD serial queue or any of a variety of other synchronization mechanisms:

class TemperatureLogger: @unchecked Sendable {                  // `@unchecked Sendable` tells the compiler that we are doing our own synchronization; this is very useful when using strict concurrency checking and/or Swift 6
    let label: String

    private var _measurements: [Int]                            // private backing variable
    var measurements: [Int] { lock.withLock { _measurements } } // synchronized computed var to return value

    private var _max: Int                                       // private backing variable
    var max: Int { lock.withLock { _max } }                     // synchronized computed var to return value

    private let lock = NSLock()                                 // or mutex or OSAllocatedLock or …

    init(label: String, measurement: Int) {
        self.label = label
        self._measurements = [measurement]
        self._max = measurement
    }

    func update(with measurement: Int) {
        lock.withLock {
            _measurements.append(measurement)
            if measurement > _max {
                _max = measurement
            }
        }
    }
}

Locks are a very performant synchronization mechanism. Serial dispatch queues are an alternative mechanism, too. Regardless of which synchronization mechanism you choose, make sure all interactions (both reads and writes) go through this synchronization mechanism. Specifically, do not expose the private backing variable, but rather only provide mechanisms (such as computed variable, update methods, etc.) that ensure the state is properly synchronized.

When considering synchronization mechanisms, you might stumble across the “reader-writer” pattern (in which one uses concurrent GCD queue, performing writes asynchronously with a barrier and performing reads synchronously without a barrier). While this is intuitively appealing, it actually offers only a modest performance improvement over a serial dispatch queue, is less performant than locks, and introduces all sorts of complicating factors, especially in thread-explosion scenarios.

See https://stackoverflow.com/a/73314198/1271826 and the links contained therein for a discussion of a variety of synchronization mechanisms.


You go on to say:

... where a number of variables are accessed in a block, but the same variables are written infrequently (such as when UI orientation or app settings change).

If it is simply for UI updates, a common technique is merely to dispatch these blocks to the main queue. This works because (a) all UI updates must happen on the main thread, anyway; and (b) the main queue is a serial queue.

In short, especially when for UI updates, dispatching related model and UI updates the main queue is a quick-and-dirty synchronization mechanism.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.