<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Iaroslav Popov</title><generator>teletype.in</generator><description><![CDATA[Iaroslav Popov]]></description><image><url>https://img4.teletype.in/files/71/d9/71d991b1-a170-4a67-ae7b-12daf7592dd1.png</url><title>Iaroslav Popov</title><link>https://teletype.in/@iaroslaw</link></image><link>https://teletype.in/@iaroslaw?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=iaroslaw</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/iaroslaw?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/iaroslaw?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Sat, 11 Apr 2026 02:09:10 GMT</pubDate><lastBuildDate>Sat, 11 Apr 2026 02:09:10 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@iaroslaw/why-you-should-stop-using-threadsafe-in-swift</guid><link>https://teletype.in/@iaroslaw/why-you-should-stop-using-threadsafe-in-swift?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=iaroslaw</link><comments>https://teletype.in/@iaroslaw/why-you-should-stop-using-threadsafe-in-swift?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=iaroslaw#comments</comments><dc:creator>iaroslaw</dc:creator><title>Why you should stop using @ThreadSafe in Swift</title><pubDate>Tue, 11 Feb 2025 14:22:04 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/ed/8d/ed8dee66-0c08-408b-9e83-f64131e83410.png"></media:content><category>swift</category><description><![CDATA[<img src="https://img4.teletype.in/files/ff/f7/fff7982a-94fa-4204-b61d-154e199c6314.png"></img>A few months ago, while tracking down a stubborn runtime crash in an iOS project, I noticed the use of a @ThreadSafe property wrapper. At first glance, it promised a convenient shortcut — simply annotate your properties, and poof, your code is safe. It’s no wonder these wrappers became popular when property wrappers were first introduced in Swift, offering an elegant way to simplify thread safety. However, despite the promise, the crashes persisted. Intrigued, I reached out to some friends and colleagues, and they mentioned that many projects employ similar approaches — sometimes called @Atomic, @Synchronized, or other variants. That realization prompted me to dig deeper and write this article.]]></description><content:encoded><![CDATA[
  <p id="4n65">A few months ago, while tracking down a stubborn runtime crash in an iOS project, I noticed the use of a <strong>@ThreadSafe</strong> property wrapper. At first glance, it promised a convenient shortcut — simply annotate your properties, and <strong>poof</strong>, your code is safe. It’s no wonder these wrappers became popular when <strong>property wrappers</strong> were first introduced in Swift, offering an elegant way to simplify <strong>thread safety</strong>. However, despite the promise, the crashes persisted. Intrigued, I reached out to some friends and colleagues, and they mentioned that many projects employ similar approaches — sometimes called <strong>@Atomic</strong>, <strong>@Synchronized</strong>, or other variants. That realization prompted me to dig deeper and write this article.</p>
  <p id="GJtX">If you’ve ever been tempted by <strong>@ThreadSafe</strong> (or its counterparts) as a <strong>“quick fix”</strong> for concurrency, this post will show you why such wrappers are dangerously misleading — and how you can achieve <strong>true thread safety</strong> instead.</p>
  <hr />
  <h3 id="PVka">Concurrency in iOS: A Blessing and a Curse</h3>
  <p id="9RZJ">In iOS development, concurrency can make your app feel smooth and responsive by handling tasks in parallel. However, misuse can lead to elusive issues:</p>
  <ul id="jyBw">
    <li id="rusp">Random crashes</li>
    <li id="40M8">UI assertion errors</li>
    <li id="dvzY">Data inconsistencies that only appear once in a thousand runs (if you’re “lucky”)</li>
  </ul>
  <p id="vNzK">These issues often surface after your app is already in the hands of users, leaving you with cryptic analytics logs and frustrated App Store reviews. So, how do these concurrency bugs creep in, and why do quick fixes like <code>@ThreadSafe</code> fail to solve them?</p>
  <h3 id="zX5g">A Popular — but Problematic — Solution</h3>
  <p id="E4wh">Here’s a <strong>simplified example</strong> of the kind of code pattern I observed (and that others confirmed seeing in their own codebases)</p>
  <pre id="pFp7" data-lang="swift"> final class Counter {
    @ThreadSafe var count: Int = 0
    @ThreadSafe var sumOfCounts: Int = 0
    
    func increment() {
        count += 1
        sumOfCounts += count
    }
}</pre>
  <p id="EMlM">At first glance, this might look harmless. Each property is marked with <code>@ThreadSafe</code>, so all reads and writes to that property should be protected, right? If you’re skeptical about whether this truly guarantees thread safety, you’re on the right track. Let’s see why.</p>
  <h3 id="EFdO">Why <code>@ThreadSafe</code> Isn’t Always Safe</h3>
  <p id="G3a3">When we annotate a property with <code>@ThreadSafe</code>, all we’re really doing is <strong>isolating</strong> the read or write operation on <strong>that single property</strong>. The moment we exit that operation, there’s no guarantee the property’s value won’t change in the meantime—especially if our class has <strong>multiple properties</strong> that need to stay in sync.</p>
  <p id="np1G">Here’s a simplified version of what a <code>@ThreadSafe</code> property wrapper might look like:</p>
  <pre id="6OPP" data-lang="swift">@propertyWrapper
struct ThreadSafe&lt;Value&gt; {
    private var value: Value
    private let lock = NSLock()

    init(wrappedValue: Value) {
        self.value = wrappedValue
    }

    var wrappedValue: Value {
        get {
            lock.lock()
            defer { lock.unlock() }
            return value
        }
        set {
            lock.lock()
            value = newValue
            lock.unlock()
        }
    }
}</pre>
  <h3 id="t9NM">Rewriting Our Class with Multiple Locks</h3>
  <p id="XcDn">You might try expanding the lock idea: lock each property individually. Here’s a more explicit approach using two separate <code>NSLock</code>s:</p>
  <pre id="eRlE" data-lang="swift">import Foundation

final class Counter {
    private let lockCount = NSLock()
    private let lockSumOfCounts = NSLock()
    
    var count: Int
    var sumOfCounts: Int
    
    init() {
        self.count = 0
        self.sumOfCounts = 0
    }
    
    func increment() {
        // Emulate &#x60;count += 1&#x60;:
        // 1) Read &#x60;count&#x60; under lock
        lockCount.lock()
        let oldCount = count
        lockCount.unlock()
        
        // 2) Write &#x60;count = oldCount + 1&#x60; under lock
        lockCount.lock()
        count = oldCount + 1
        lockCount.unlock()
        
        // Emulate &#x60;sumOfCounts += count&#x60;:
        // 1) Read &#x60;sumOfCounts&#x60; under its lock
        lockSumOfCounts.lock()
        let oldSum = sumOfCounts
        lockSumOfCounts.unlock()
        
        // 2) Read &#x60;count&#x60; again (for the sumOfCounts calculation)
        lockCount.lock()
        let currentCount = count
        lockCount.unlock()
        
        // 3) Write &#x60;sumOfCounts = oldSum + currentCount&#x60; under lock
        lockSumOfCounts.lock()
        sumOfCounts = oldSum + currentCount
        lockSumOfCounts.unlock()
    }
}</pre>
  <h3 id="nekI">The Hidden Problem</h3>
  <p id="HG17">Even though this looks more “locked down,” it involves <strong>five</strong> separate lock interactions. Once a lock is released, the value you just read can already be outdated. We also can’t rely on properties protected by different locks remaining in sync. Using separate locks for each property <strong>prevents simultaneous writes on one property</strong> but still leaves your class vulnerable to race conditions when multiple properties must stay consistent.</p>
  <hr />
  <h2 id="SOd4">Safer Approaches to Concurrency</h2>
  <h3 id="BCQH">1. Use a Single Lock for the Whole Operation</h3>
  <p id="WFdN">A straightforward way to keep your properties consistent is to use a <strong>single lock</strong> that guards all relevant state:</p>
  <pre id="b746" data-lang="swift">import Foundation

final class Counter {
    private let lock = NSLock()
    
    private var count: Int
    private var sumOfCounts: Int
    
    init() {
        self.count = 0
        self.sumOfCounts = 0
    }
    
    func increment() {
        lock.lock()
        count += 1
        sumOfCounts += count
        lock.unlock()
    }
    
    func currentValues() -&gt; (Int, Int) {
        var values = (0, 0)
        lock.lock()
        values = (self.count, self.sumOfCounts)
        lock.unlock()
        return values
    }
}</pre>
  <p id="Myrr">Here, we lock once at the start of <code>increment()</code>, do all necessary reads and writes, and then unlock. Because <strong>all</strong> shared data is protected by the <strong>same</strong> lock, we avoid a situation where partial updates slip through.</p>
  <h3 id="IAdG">2. Use a Dedicated Serial Queue</h3>
  <p id="17UF">If your operation is CPU-heavy or needs to run asynchronously, consider using a <strong>serial <code>DispatchQueue</code></strong>:</p>
  <pre id="t2jc" data-lang="swift">import Foundation

final class Counter {
    private let queue = DispatchQueue(label: &quot;com.example.counterQueue&quot;)
    private var count = 0
    private var sumOfCounts = 0

    func increment() {
        queue.async {
            self.count += 1
            self.sumOfCounts += self.count
        }
    }
    
    func currentValues(completion: @escaping (Int, Int) -&gt; Void) {
        queue.async {
            completion(self.count, self.sumOfCounts)
        }
    }
}</pre>
  <p id="7iEN">A dedicated serial queue ensures only one piece of work is executed at a time, effectively avoiding data races without manually locking and unlocking.</p>
  <h3 id="ag10">3. Use Swift Concurrency (Actors)</h3>
  <blockquote id="uIjh"><em><strong>Disclaimer</strong>: This approach requires Swift 5.5 or later, and a deployment target that supports Swift Concurrency (e.g., iOS 15+). If your project can’t yet adopt modern concurrency, the first two approaches remain valid.</em></blockquote>
  <p id="0Sov">If you’re able to take advantage of Swift Concurrency, <strong>actors</strong> can greatly simplify your thread-safety code:</p>
  <pre id="Rs5l" data-lang="swift">actor Counter {
    private var count = 0
    private var sumOfCounts = 0

    func increment() {
        count += 1
        sumOfCounts += count
    }

    func currentValues() -&gt; (Int, Int) {
        (count, sumOfCounts)
    }
}</pre>
  <p id="bWJc">Actors in Swift automatically protect their state from concurrent access. You don’t have to manually lock and unlock anything; Swift ensures that mutable state within the actor is accessed safely. This drastically reduces the risk of race conditions and lock-related bugs while keeping the code more readable.</p>
  <hr />
  <h3 id="zpfj">Conclusion &amp; Key Takeaways</h3>
  <ol id="aDrS">
    <li id="9bHw"><strong>Property-Level Locking Isn’t Enough</strong><br />Locking each property individually can lead to race conditions when multiple properties must remain consistent.</li>
    <li id="jxZT"><strong>Use a Single Lock, Serial Queue, or Swift Actors</strong><br />If a set of operations must be performed together atomically, consider using one lock, a dedicated serial queue, or Swift’s built-in concurrency features (actors). Each of these approaches ensures your data remains consistent across multiple properties, preventing the subtle concurrency bugs that arise when locking individual properties or relying on partial updates.</li>
    <li id="qcis"><strong>Aim for Logical Consistency</strong><br />Concurrency is not just about preventing simultaneous reads or writes on individual variables; it’s also about maintaining the logical invariants of your data.</li>
    <li id="wnzz"><strong>Be Mindful of Performance vs. Safety</strong><br />Multiple, fine-grained locks can introduce complexity and even reduce performance. Often, a single well-placed lock, a serial queue, or an actor is both safer and simpler.</li>
  </ol>
  <p id="xo59">By avoiding quick fixes like <code>@ThreadSafe</code> and focusing on the bigger picture—logical consistency—you’ll be able to design concurrency that keeps your app stable, maintainable, and responsive. Concurrency is a powerful tool, but it demands a careful, holistic approach to prevent lurking runtime crashes and data inconsistencies.</p>
  <hr />
  <p id="O2LY"><strong>Thanks for reading!</strong> If you have any questions, experiences, or tips of your own about concurrency in Swift, feel free to share in the comments.</p>

]]></content:encoded></item></channel></rss>