# SwiftUI - TDD actor to avoid race condition

## Problem: How to avoid threading race conditions in your class?

### Solution: Use actor that handles all the concurrency for you!

Code that runs synchronously is easy to manage and understand  
But when start introducing threads, problems occur  
You need to manage access to the read/write of a property  
Using a serial queue to avoid race condition

But with `actor` this will be a lot more simple  
It handles all concurrency for you 🤩

---

We use Unit Test to prove that everything is working fine

---

First, create a simple `Contact` model  
It will be used for read/write operation in memory

```swift
struct Contact: Equatable {
  let id = UUID().uuidString
  let name: String
}
```

---

Create a simple class `ContactCache`  
It will synchronously read/write contacts in memory

```swift
class ContactCache {
  private var dictContacts = [String: Contact]()

  func save(_ contact: Contact) {
    dictContacts[contact.id] = contact
  }

  func load(_ contact: Contact) -> Contact? {
    dictContacts[contact.id]
  }
}
```

---

Write a test that will save and load a contact

```swift
final class ActorTests: XCTestCase {
  func test_save_saveContactInCache() {
    let contact = Contact(name: "A name")
    let sut = ContactCache()
    
    sut.save(contact)
    let contactCache = sut.load(contact)
    
    XCTAssertEqual(contactCache, contact)
  }
}
```

Run the test and it passes  
With synchronous behavior everything is working fine  
Now introduce some concurrency using the `global()` queue

```swift
class ContactCache {
  private var dictContacts = [String: Contact]()
  
  func save(_ contact: Contact) {
    DispatchQueue.global().async { // 1
      self.dictContacts[contact.id] = contact
    }
  }
  
  func load(_ contactId: String, completion: @escaping (Contact?) -> Void) { // 2
    DispatchQueue.global().async { // 1
      completion(self.dictContacts[contactId])
    }
  }
}
```

1. Dispatch the read/write operation to the `global` queue to avoid to block the main thread
    
2. Unfortunately, we need to add a completion handler to return the result when the operation is finished, which complicates our code
    

Now update the test:

```swift
  func test_save_saveContactInCache() {
    let contact = Contact(name: "A name")
    let sut = ContactCache()
    
    sut.save(contact)
    
    let exp = XCTestExpectation(description: "Expect that load completes")
    var contactCache: Contact?
    sut.load(contact.id) { contact in
      contactCache = contact
      exp.fulfill()
    }
    
    wait(for: [exp], timeout: 0.1) // 1
    
    XCTAssertEqual(contactCache, contact)
  }
```

1. We need to wait for load completion
    

Run the test and it fails with a crash  
*Thread 7: EXC\_BAD\_ACCESS (code=1, address=0x18)*  
The thread 7 is a concurrent queue  
We try to load contact during the save operation so the app crashed

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1673473230674/c570d81f-325a-43f1-88ef-ab8874195ede.png align="center")

To make the test pass we need to protect read/write operations  
Using a serial queue

```swift
class ContactCache {
  private var dictContacts = [String: Contact]()
  private let serialQueue = DispatchQueue(label: "contact.cache") // 1
  
  func save(_ contact: Contact) {
    DispatchQueue.global().async { 
      self.serialQueue.async { // 2
        self.dictContacts[contact.id] = contact
      }
    }
  }
  
  func load(_ contactId: String, completion: @escaping (Contact?) -> Void) {
    DispatchQueue.global().async {
      self.serialQueue.async { // 2
        completion(self.dictContacts[contactId])
      }
    }
  }
}
```

1. Create a `DispatchQueue` that is serial by default so the code inside it will run synchronously
    
2. Make read/write operations in the `serialQueue` so if the save operation is running, the load operation has to wait until the end of it
    

Run the tests as many times as you want  
They always pass

Ok good but this class now looks a bit messy

1. We need to add global queues everywhere
    
2. We need to add a completion handler to the `load()` function
    
3. We need to handle manually the synchronous behavior of this class
    

Because of this mess, it's time for the `actor` to comes in

---

Start by deleting all the `global()` queues and also the `serialQueue`  
Replace the `class` keyword by `actor`

```swift
actor ContactCache { // 1
  private var dictContacts = [String: Contact]()
  
  func save(_ contact: Contact) {
    self.dictContacts[contact.id] = contact
  }
  
  func load(_ contactId: String) -> Contact? {
    return self.dictContacts[contactId]
  }
}
```

1. The `class` keyword has been replaced by the `actor` keyword. Now all functions will behave like `async` function
    

Update also the test, deleting the completion handler and the expectation

```swift
  func test_save_saveContactInCache() {
    let contact = Contact(name: "A name")
    let sut = ContactCache()
    
    sut.save(contact) // 1
    
    let contactCache = sut.load(contact.id) // 1
    
    XCTAssertEqual(contactCache, contact)
  }
```

1. Compiler help guide us with errors:  
    *Actor-isolated instance method 'save' can not be referenced from a non-isolated context*
    
    and  
    *Actor-isolated instance method 'load' can not be referenced from a non-isolated context*
    

We need to use actor functions like `async/await`

```swift
  func test_save_saveContactInCache() {
    let contact = Contact(name: "A name")
    let sut = ContactCache()
    
    Task { // 1
      await sut.save(contact) // 2
    }
    
    Task { // 1
      let contactCache = await sut.load(contact.id) // 2
      XCTAssertEqual(contactCache, contact)
    }
  }
```

1. Encapsulate the logic of functions into a `Task`
    
2. Add the `await` keyword because they are now `async` functions
    

You can run the test and it passes as much as you want the will always pass  
The `load()` function will always wait that the `save()` function completes

---

The `actor` feature helps a lot to deal with all concurrency problems  
Eliminating global/serial queues and completion callback hell  
Also, avoid a lot of crashes and future complicated debug sessions  
I hope you enjoy this journey with `actor` and you will use it in your project

That's it !! 🎉
