Close Menu
  • Home
  • AI
  • Big Data
  • Cloud Computing
  • iOS Development
  • IoT
  • IT/ Cybersecurity
  • Tech
    • Nanotechnology
    • Green Technology
    • Apple
    • Software Development
    • Software Engineering

Subscribe to Updates

Get the latest technology news from Bigteetechhub about IT, Cybersecurity and Big Data.

    What's Hot

    When hard work pays off

    October 14, 2025

    “Bunker Mentality” in AI: Are We There Yet?

    October 14, 2025

    Israel Hamas deal: The hostage, ceasefire, and peace agreement could have a grim lesson for future wars.

    October 14, 2025
    Facebook X (Twitter) Instagram
    Facebook X (Twitter) Instagram
    Big Tee Tech Hub
    • Home
    • AI
    • Big Data
    • Cloud Computing
    • iOS Development
    • IoT
    • IT/ Cybersecurity
    • Tech
      • Nanotechnology
      • Green Technology
      • Apple
      • Software Development
      • Software Engineering
    Big Tee Tech Hub
    Home»iOS Development»Using singletons in Swift 6 – Donny Wals
    iOS Development

    Using singletons in Swift 6 – Donny Wals

    big tee tech hubBy big tee tech hubApril 26, 20250010 Mins Read
    Share Facebook Twitter Pinterest Copy Link LinkedIn Tumblr Email Telegram WhatsApp
    Follow Us
    Google News Flipboard
    Using singletons in Swift 6 – Donny Wals
    Share
    Facebook Twitter LinkedIn Pinterest Email Copy Link


    Singletons generally speaking get a bad rep. People don’t like them, they cause issues, and generally speaking it’s just not great practice to rely on globally accessible mutable state in your apps. Instead, it’s more favorable to practice explicit dependency passing which makes your code more testable and reliable overall.

    That said, sometimes you’ll have singletons. Or, more likely, you’ll want to have a a shared instance of something that you need in a handful of places in your app:

    class AuthProvider {
      static let shared = AuthProvider()
    
      // ...
    }

    In Swift 6, this will lead to issues because Swift 6 doesn’t like non-Sendable types, and it also doesn’t like global mutable state.

    In this post, you’ll learn about the reasons that Swift 6 will flag your singletons and shared instances as problematic, and we’ll see what you can do to satisfy the Swift 6 compiler. We’ll run through several different errors that you can get for your shared instances depending on how you’ve structured your code.

    Static property ‘shared’ is not concurrency-safe because it is nonisolated global shared mutable state

    We’ll start off with an error that you’ll get for any static property that’s mutable regardless of whether this property is used for a shared instance or not.

    For example:

    class AuthProvider {
      // Static property 'shared' is not concurrency-safe because it 
      // is nonisolated global shared mutable state
      static var shared = AuthProvider()
    
      private init() {}
    }
    
    class GamePiece {
      // Static property 'power' is not concurrency-safe because it 
      // is nonisolated global shared mutable state
      static var power = 100
    }

    As you can see, both GamePiece and AuthProvider get the exact same error. They’re not concurrency-safe because they’re not isolated and they’re mutable. That means we might mutate this static let from multiple tasks and that would lead to data races (and crashes).

    To resolve this error, we can take different approaches depending on the usage of our static var. If we really need our static member to be mutable, we should make sure that we can safely mutate and that means we need to isolate our mutable state somehow.

    Resolving the error when our static var needs to be mutable

    We’ll start off by looking at our GamePiece; it really needs power to be mutable because we can upgrade its value throughout the imaginary game I have in mind.

    Isolating GamePiece to the main actor

    One approach is to isolate our GamePiece or static var power to the main actor:

    // we can isolate our GamePiece to the main actor
    @MainActor
    class GamePiece {
      static var power = 100
    }
    
    // or we isolate the static var to the main actor
    class GamePiece {
      @MainActor
      static var power = 100
    }

    The first option makes sense when GamePiece is a class that’s designed to closely work with our UI layer. When we only ever work with GamePiece from the UI, it makes sense to isolate the entire object to the main actor. This simplifies our code and makes it so that we’re not going from the main actor’s isolation to some other isolation and back all the time.

    Alternatively, if we don’t want or need the entire GamePiece to be isolated to the main actor we can also choose to only isolate our static var to the main actor. This means that we’re reading and writing power from the main actor at all times, but we can work with other methods an properties on GamePiece from other isolation contexts too. This approach generally leads to more concurrency in your app, and it will make your code more complex overall.

    There’s a second option that we can reach for, but it’s one that you should only use if constraining your type to a global actor makes no sense.

    It’s nonisolated(unsafe).

    Allowing static var with nonisolated(unsafe)

    Sometimes you’ll know that your code is safe. For example, you might know that power is only accessed from a single task at a time, but you don’t want to encode this into the type by making the property main actor isolated. This makes sense because maybe you’re not accessing it from the main actor but you’re using a global dispatch queue or a detached task.

    In these kinds of situations the only real correct solution would be to make GamePiece an actor. But this is often non-trivial, introduces a lot of concurrency, and overall makes things more complex. When you’re working on a new codebase, the consequences wouldn’t be too bad and your code would be more “correct” overall.

    In an existing app, you usually want to be very careful about introducing new actors. And if constraining to the main actor isn’t an option you might need an escape hatch that tells the compiler “I know you don’t like this, but it’s okay. Trust me.”. That escape hatch is nonisolated(unsafe):

    class GamePiece {
      nonisolated(unsafe) static var power = 100
    }

    When you mark a static var as nonisolated(unsafe) the compiler will no longer perform data-race protection checks for that property and you’re free to use it however you please.

    When things are working well, that’s great. But it’s also risky; you’re now taking on the manual responsibility of prevent data races. And that’s a shame because Swift 6 aims to help us catch potential data races at compile time!

    So use nonisolated(unsafe) sparingly, mindfully, and try to get rid of it as soon as possible in favor of isolating your global mutable state to an actor.

    Note that in Swift 6.1 you could make GamePiece an actor and the Swift compiler will allow you to have static var power = 100 without issues. This is a bug in the compiler and still counts as a potential data race. A fix has already been merged to Swift’s main branch so I would expect that Swift 6.2 emits an appropriate error for having a static var on an actor.

    Resolving the error for shared instances

    When you’re working with a shared instance, you typically don’t need the static var to be a var at all. When that’s the case, you can actually resolve the original error quite easily:

    class AuthProvider {
      static let shared = AuthProvider()
    
      private init() {}
    }

    Make the property a let instead of a var and Static property 'shared' is not concurrency-safe because it is nonisolated global shared mutable state goes away.

    A new error will appear though…

    Static property ‘shared’ is not concurrency-safe because non-‘Sendable’ type ‘AuthProvider’ may have shared mutable state

    Let’s dig into that error next.

    Static property ‘shared’ is not concurrency-safe because non-‘Sendable’ type may have shared mutable state

    While the new error sounds a lot like the one we had before, it’s quite different. The first error complained that the static var itself wasn’t concurrency-safe, this new error isn’t complaining about the static let itself. It’s complaining that we have a globally accessible instance of our type (AuthProvider) which might not be safe to interact with from multiple tasks.

    If multiple tasks attempt to read or mutate state on our instance of AuthProvider, every task would interact with the exact same instance. So if AuthProvider can’t handle that correctly, we’re in trouble.

    The way to fix this, is to make AuthProvider a Sendable type. If you’re not sure that you fully understand Sendable just yet, make sure to read this post about Sendable so you’re caught up.

    The short version of Sendable is that a Sendable type is a type that is safe to interact with from multiple isolation contexts.

    Making AuthProvider Sendable

    For reference types like our AuthProvider being Sendable would mean that:

    • AuthProvider can’t have any mutable state
    • All members of AuthProvider must also be Sendable
    • AuthProvider must be a final class
    • We manually conform AuthProvider to the Sendable protocol

    In the sample code, AuthProvider didn’t have any state at all. So if we’d fix the error for our sample, I would be able to do the following:

    final class AuthProvider: Sendable {
      static let shared = AuthProvider()
    
      private init() {}
    }

    By making AuthProvider a Sendable type, the compiler will allow us to have a shared instance without any issues because the compiler knows that AuthProvider can safely be used from multiple isolation contexts.

    But what if we add some mutable state to our AuthProvider?

    final class AuthProvider: Sendable {
      static let shared = AuthProvider()
    
      // Stored property 'currentToken' of 
      // 'Sendable'-conforming class 'AuthProvider' is mutable
      private var currentToken: String?
    
      private init() {}
    }

    The compiler does not allow our Sendable type to have mutable state. It doesn’t matter that this state is private, it’s simply not allowed.

    Using nonisolated(unsafe) as an escape hatch again

    If we have a shared instance with mutable state, we have several options available to us. We could remove the Sendable conformance and make our static let a nonisolated(unsafe) property:

    class AuthProvider {
      nonisolated(unsafe) static let shared = AuthProvider()
    
      private var currentToken: String?
    
      private init() {}
    }

    This works but it’s probably the worst option we have because it doesn’t protect our mutable state from data races.

    Leveraging a global actor to make AuthProvider Sendable

    Alternatively, we could apply isolate our type to the main actor just like we did with our static var:

    // we can isolate our class
    @MainActor
    class AuthProvider {
      static let shared = AuthProvider()
    
      private var currentToken: String?
    
      private init() {}
    }
    
    // or just the shared instance
    class AuthProvider {
      @MainActor
      static let shared = AuthProvider()
    
      private var currentToken: String?
    
      private init() {}
    }

    The pros and cons of this solutions are the same as they were for the static var. If we mostly use AuthProvider from the main actor this is fine, but if we frequently need to work with AuthProvider from other isolation contexts it becomes a bit of a pain.

    Making AuthProvider an actor

    My preferred solution is to either make AuthProvider conform to Sendable like I showed earlier, or to make AuthProvider into an actor:

    actor AuthProvider {
      static let shared = AuthProvider()
    
      private var currentToken: String?
    
      private init() {}
    }

    Actors in Swift are always Sendable which means that an actor can always be used as a static let.

    There’s one more escape hatch…

    Let’s say we can’t make AuthProvider an actor because we’re working with existing code and we’re not ready to pay the price of introducing loads of actor-related concurrency into our codebase.

    Maybe you’ve had AuthProvider in your project for a while and you’ve taken appropriate measures to ensure its concurrency-safe.

    If that’s the case, @unchecked Sendable can help you bridge the gap.

    Using @unchecked Sendable as an escape hatch

    Marking our class as @unchecked Sendable can be done as follows:

    final class AuthProvider: @unchecked Sendable {
      static let shared = AuthProvider()
    
      private var currentToken: String?
    
      private init() {}
    }

    An escape hatch like this should be used carefully and should ideally be considered a temporary fix. The compiler won’t complain but you’re open to data-races that the compiler can help prevent altogether; it’s like a sendability force-unwrap.

    In Summary

    Swift 6 allows singletons, there’s no doubt about that. It does, however, impose pretty strict rules on how you define them, and Swift 6 requires you to make sure that your singletons and shared instances are safe to use from multiple tasks (isolation contexts) at the same time.

    In this post, you’ve seen several ways to get rid of two shared instance related errors.

    First, you saw how you can have static var members in a way that’s concurrency-safe by leveraging actor isolation.

    Next, you saw that static let is another way to have a concurrency-safe static member as long as the type of your static let is concurrency-safe. This is what you’ll typically use for your shared instances.

    I hope this post has helped you grasp static members and Swift 6 a bit better, and that you’re now able to leverage actor isolation where needed to correctly have global state in your apps.



    Source link

    Donny singletons Swift Wals
    Follow on Google News Follow on Flipboard
    Share. Facebook Twitter Pinterest LinkedIn Tumblr Email Copy Link
    tonirufai
    big tee tech hub
    • Website

    Related Posts

    ios – Differences in builds between Xcode 16.4 and Xcode 26

    October 13, 2025

    ios – Apple mapkit route function dose not works in China

    October 12, 2025

    swift – Does UIDevice.current.identifierForVendor change after iCloud backup and restore on another iOS device?

    October 11, 2025
    Add A Comment
    Leave A Reply Cancel Reply

    Editors Picks

    When hard work pays off

    October 14, 2025

    “Bunker Mentality” in AI: Are We There Yet?

    October 14, 2025

    Israel Hamas deal: The hostage, ceasefire, and peace agreement could have a grim lesson for future wars.

    October 14, 2025

    Astaroth: Banking Trojan Abusing GitHub for Resilience

    October 13, 2025
    Advertisement
    About Us
    About Us

    Welcome To big tee tech hub. Big tee tech hub is a Professional seo tools Platform. Here we will provide you only interesting content, which you will like very much. We’re dedicated to providing you the best of seo tools, with a focus on dependability and tools. We’re working to turn our passion for seo tools into a booming online website. We hope you enjoy our seo tools as much as we enjoy offering them to you.

    Don't Miss!

    When hard work pays off

    October 14, 2025

    “Bunker Mentality” in AI: Are We There Yet?

    October 14, 2025

    Subscribe to Updates

    Get the latest technology news from Bigteetechhub about IT, Cybersecurity and Big Data.

      • About Us
      • Contact Us
      • Disclaimer
      • Privacy Policy
      • Terms and Conditions
      © 2025 bigteetechhub.All Right Reserved

      Type above and press Enter to search. Press Esc to cancel.