iOS Concurrency with GCD & Operations

Sep 12 2023 · Swift 5.8, macOS 13, iOS 16, Xcode 14.3

Part 2: Concurrency Problems & Solutions

13. Make Class Thread-safe

Episode complete

Play next episode

Next
About this episode
Leave a rating/review
See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 12. Explore Priority Inversion Next episode: 14. Challenge: Make Number Class Thread-Safe

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Heads up... You’re accessing parts of this content for free, with some sections shown as obfuscated text.

Heads up... You’re accessing parts of this content for free, with some sections shown as obfuscated text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Simple example

Build and run the DataRace project in the starter folder. The screen just displays the default message Hello, world! while the two for-loops in raceYou() run until these messages appear in the console:

>>> Main Queue counter = 19
>>> notMain Queue counter = 20
var counter = 0

let queue = DispatchQueue(label: "notMain")
queue.async {
  for _ in 1 ... 10 {
    Thread.sleep(forTimeInterval: Double.random(in: 0.1..<0.5))
    counter += 1
  }
  print(">>> notMain Queue counter = \(counter)")
}

DispatchQueue.main.async {
  for _ in 1 ... 10 {
    Thread.sleep(forTimeInterval: Double.random(in: 0.1..<0.5))
    counter += 1
  }
  print(">>> Main Queue counter = \(counter)")
}
WARNING: ThreadSanitizer: Swift access race (pid=13189)
...
SUMMARY: ThreadSanitizer: Swift access race ... in ContentView.raceYou()
==================
ThreadSanitizer report breakpoint hit.

Thread-safe Class Playground

An object type is thread-safe if concurrent tasks can access its instances without causing any concurrency problems. If a variable or mutable data structure is not thread-safe, you should access it from only one thread at a time.

"Brian Biggles" (in sidebar)
for (idx, name) in nameList.enumerated() {
  workerQueue.async(group: nameChangeGroup) {
    usleep(UInt32(10_000 * idx))
    nameChangingPerson.changeName(firstName: name.0, lastName: name.1)
    print("Current Name: \(nameChangingPerson.name)")
  }
}
nameChangeGroup.notify(queue: DispatchQueue.global()) {
  print("Final name: \(nameChangingPerson.name)")
  PlaygroundPage.current.finishExecution()
}

nameChangeGroup.wait()
Current Name: Freddie Dingle
Current Name: Freddie Evershed
Current Name: Freddie Gregory
Current Name: Freddie Frost
Current Name: Freddie Cheesecake
Final name: Freddie Cheesecake
class ThreadSafePerson: Person {
  
}
let isolationQueue = DispatchQueue(
  label: "com.kodeco.person.isolation", 
  attributes: .concurrent)
override func changeName(firstName: String, lastName: String) {
  isolationQueue.async(flags: .barrier) {
    // barrier task
    super.changeName(firstName: firstName, lastName: lastName)
  }
}
override var name: String {
  isolationQueue.sync {
    super.name
  }
}
print("\n=== Threadsafe ===")
let threadSafeNameGroup = DispatchGroup()
let threadSafePerson = ThreadSafePerson(firstName: "Anna", lastName: "Adams")
for (idx, name) in nameList.enumerated() {
  workerQueue.async(group: threadSafeNameGroup) {
    usleep(UInt32(10_000 * idx))
    threadSafePerson.changeName(firstName: name.0, lastName: name.1)
    print("Current threadsafe name: \(threadSafePerson.name)")
  }
}

threadSafeNameGroup.notify(queue: DispatchQueue.global()) {
  print("Final threadsafe name: \(threadSafePerson.name)")
  sleep(1)
  PlaygroundPage.current.finishExecution()
}
=== Threadsafe ===
Current threadsafe name: Eva Evershed
Current threadsafe name: Eva Evershed
Current threadsafe name: Eva Evershed
Current threadsafe name: Freddie Frost
Current threadsafe name: Gina Gregory
Final threadsafe name: Gina Gregory

Thread-safe Class Project

Now look at an app version of this code, in NameChanger: It has only a placeholder UI, but you need an app to use TSan. As you did with the DataRace project, edit the scheme to enable TSan, then build and run.

// TSan finds race condition errors
// changeNameRace()

// TSan finds no errors
changeNameSafely()
Current threadsafe name: Charlie Cheesecake
Current threadsafe name: Delia Dingle
Current threadsafe name: Eva Evershed
Current threadsafe name: Freddie Frost
Current threadsafe name: Gina Gregory
Final threadsafe name: Gina Gregory