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

    Setting Up a Google Colab AI-Assisted Coding Environment That Actually Works

    March 11, 2026

    The economics of enterprise AI: What the Forrester TEI study reveals about Microsoft Foundry

    March 11, 2026

    The search for new bosons beyond Higgs – Physics World

    March 11, 2026
    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»Exploring concurrency changes in Swift 6.2 – Donny Wals
    iOS Development

    Exploring concurrency changes in Swift 6.2 – Donny Wals

    big tee tech hubBy big tee tech hubMay 21, 20250211 Mins Read
    Share Facebook Twitter Pinterest Copy Link LinkedIn Tumblr Email Telegram WhatsApp
    Follow Us
    Google News Flipboard
    Exploring concurrency changes in Swift 6.2 – Donny Wals
    Share
    Facebook Twitter LinkedIn Pinterest Email Copy Link


    It’s no secret that Swift concurrency can be pretty difficult to learn. There are a lot of concepts that are different from what you’re used to when you were writing code in GCD. Apple recognized this in one of their vision documents and they set out to make changes to how concurrency works in Swift 6.2. They’re not going to change the fundamentals of how things work. What they will mainly change is where code will run by default.

    In this blog post, I would like to take a look at the two main features that will change how your Swift concurrency code works:

    1. The new nonisolated(nonsending) default feature flag
    2. Running code on the main actor by default with the defaultIsolation setting

    By the end of this post you should have a pretty good sense of the impact that Swift 6.2 will have on your code, and how you should be moving forward until Swift 6.2 is officially available in a future Xcode release.

    Understanding nonisolated(nonsending)

    The nonisolated(nonsending) feature is introduced by SE-0461 and it’s a pretty big overhaul in terms of how your code will work moving forward. At the time of writing this, it’s gated behind an upcoming feature compiler flag called NonisolatedNonsendingByDefault. To enable this flag on your project, see this post on leveraging upcoming features in an SPM package, or if you’re looking to enable the feature in Xcode, take a look at enabling upcoming features in Xcode.

    For this post, I’m using an SPM package so my Package.swift contains the following:

    .executableTarget(
        name: "SwiftChanges",
        swiftSettings: [
            .enableExperimentalFeature("NonisolatedNonsendingByDefault")
        ]
    )

    I’m getting ahead of myself though; let’s talk about what nonisolated(nonsending) is, what problem it solves, and how it will change the way your code runs significantly.

    Exploring the problem with nonisolated in Swift 6.1 and earlier

    When you write async functions in Swift 6.1 and earlier, you might do so on a class or struct as follows:

    class NetworkingClient {
      func loadUserPhotos() async throws -> [Photo] {
        // ...
      }
    }

    When loadUserPhotos is called, we know that it will not run on any actor. Or, in more practical terms, we know it’ll run away from the main thread. The reason for this is that loadUserPhotos is a nonisolated and async function.

    This means that when you have code as follows, the compiler will complain about sending a non-sendable instance of NetworkingClient across actor boundaries:

    struct SomeView: View {
      let network = NetworkingClient()
    
      var body: some View {
        Text("Hello, world")
          .task { await getData() }
      }
    
      func getData() async {
        do {
          // sending 'self.network' risks causing data races
          let photos = try await network.loadUserPhotos()
        } catch {
          // ...
        }
      }
    }

    When you take a closer look at the error, the compiler will explain:

    sending main actor-isolated ‘self.network’ to nonisolated instance method ‘loadUserPhotos()’ risks causing data races between nonisolated and main actor-isolated uses

    This error is very similar to one that you’d get when sending a main actor isolated value into a sendable closure.

    The problem with this code is that loadUserPhotos runs in its own isolation context. This means that it will run concurrently with whatever the main actor is doing.

    Since our instance of NetworkingClient is created and owned by the main actor we can access and mutate our networking instance while loadUserPhotos is running in its own isolation context. Since that function has access to self, it means that we can have two isolation contexts access the same instance of NetworkingClient at the exact same time.

    And as we know, multiple isolation contexts having access to the same object can lead to data races if the object isn’t sendable.

    The difference between an async and non-async function that’s nonisolated like loadUserPhotos is that the non-async function would run on the caller’s actor. So if we call a nonisolated async function from the main actor then the function will run on the main actor. When we call a nonisolated async function from a place that’s not on the main actor, then the called function will not run on the main actor.

    Swift 6.2 aims to fix this with a new default for nonisolated functions.

    Understanding nonisolated(nonsending)

    The behavior in Swift 6.1 and earlier is inconsistent and confusing for folks, so in Swift 6.2, async functions will adopt a new default for nonisolated functions called nonisolated(nonsending). You don’t have to write this manually; it’s the default so every nonisolated async function will be nonsending unless you specify otherwise.

    When a function is nonisolated(nonsending) it means that the function won’t cross actor boundaries. Or, in a more practical sense, a nonisolated(nonsending) function will run on the caller’s actor.

    So when we opt-in to this feature by enabling the NonisolatedNonsendingByDefault upcoming feature, the code we wrote earlier is completely fine.

    The reason for that is that loadUserPhotos() would now be nonisolated(nonsending) by default, and it would run its function body on the main actor instead of running it on the cooperative thread pool.

    Let’s take a look at some examples, shall we? We saw the following example earlier:

    class NetworkingClient {
      func loadUserPhotos() async throws -> [Photo] {
        // ...
      }
    }

    In this case, loadUserPhotos is both nonisolated and async. This means that the function will receive a nonisolated(nonsending) treatment by default, and it runs on the caller’s actor (if any). In other words, if you call this function on the main actor it will run on the main actor. Call it from a place that’s not isolated to an actor; it will run away from the main thread.

    Alternatively, we might have added a @MainActor declaration to NetworkingClient:

    @MainActor
    class NetworkingClient {
      func loadUserPhotos() async throws -> [Photo] {
        return [Photo()]
      }
    }

    This makes loadUserPhotos isolated to the main actor so it will always run on the main actor, no matter where it’s called from.

    Then we might also have the main actor annotation along with nonisolated on loadUserPhotos:

    @MainActor
    class NetworkingClient {
      nonisolated func loadUserPhotos() async throws -> [Photo] {
        return [Photo()]
      }
    }

    In this case, the new default kicks in even though we didn’t write nonisolated(nonsending) ourselves. So, NetworkingClient is main actor isolated but loadUserPhotos is not. It will inherit the caller’s actor. So, once again if we call loadUserPhotos from the main actor, that’s where we’ll run. If we call it from some other place, it will run there.

    So what if we want to make sure that our function never runs on the main actor? Because so far, we’ve only seen possibilities that would either isolate loadUserPhotos to the main actor, or options that would inherit the callers actor.

    Running code away from any actors with @concurrent

    Alongside nonisolated(nonsending), Swift 6.2 introduces the @concurrent keyword. This keyword will allow you to write functions that behave in the same way that your code in Swift 6.1 would have behaved:

    @MainActor
    class NetworkingClient {
      @concurrent
      nonisolated func loadUserPhotos() async throws -> [Photo] {
        return [Photo()]
      }
    }

    By marking our function as @concurrent, we make sure that we always leave the caller’s actor and create our own isolation context.

    The @concurrent attribute should only be applied to functions that are nonisolated. So for example, adding it to a method on an actor won’t work unless the method is nonisolated:

    actor SomeGenerator {
      // not allowed
      @concurrent
      func randomID() async throws -> UUID {
        return UUID()
      }
    
      // allowed
      @concurrent
      nonisolated func randomID() async throws -> UUID {
        return UUID()
      }
    }

    Note that at the time of writing both cases are allowed, and the @concurrent function that’s not nonisolated acts like it’s not isolated at runtime. I expect that this is a bug in the Swift 6.2 toolchain and that this will change since the proposal is pretty clear about this.

    How and when should you use NonisolatedNonSendingByDefault

    In my opinion, opting in to this upcoming feature is a good idea. It does open you up to a new way of working where your nonisolated async functions inherit the caller’s actor instead of always running in their own isolation context, but it does make for fewer compiler errors in practice, and it actually helps you get rid of a whole bunch of main actor annotation based on what I’ve been able to try so far.

    I’m a big fan of reducing the amount of concurrency in my apps and only introducing it when I want to explicitly do so. Adopting this feature helps a lot with that. Before you go and mark everything in your app as @concurrent just to be sure; ask yourself whether you really have to. There’s probably no need, and not running everything concurrently makes your code, and its execution a lot easier to reason about in the big picture.

    That’s especially true when you also adopt Swift 6.2’s second major feature: defaultIsolation.

    Exploring Swift 6.2’s defaultIsolation options

    In Swift 6.1 your code only runs on the main actor when you tell it to. This could be due to a protocol being @MainActor annotated or you explicitly marking your views, view models, and other objects as @MainActor.

    Marking something as @MainActor is a pretty common solution for fixing compiler errors and it’s more often than not the right thing to do.

    Your code really doesn’t need to do everything asynchronously on a background thread.

    Doing so is relatively expensive, often doesn’t improve performance, and it makes your code a lot harder to reason about. You wouldn’t have written DispatchQueue.global() everywhere before you adopted Swift Concurrency, right? So why do the equivalent now?

    Anyway, in Swift 6.2 we can make running on the main actor the default on a package level. This is a feature introduced by SE-0466.

    This means that you can have UI packages and app targets and model packages etc, automatically run code on the main actor unless you explicitly opt-out of running on main with @concurrent or through your own actors.

    Enable this feature by setting defaultIsolation in your swiftSettings or by passing it as a compiler argument:

    swiftSettings: [
        .defaultIsolation(MainActor.self),
        .enableExperimentalFeature("NonisolatedNonsendingByDefault")
    ]

    You don’t have to use defaultIsolation alongside NonisolatedNonsendingByDefault but I did like to use both options in my experiments.

    Currently you can either pass MainActor.self as your default isolation to run everything on main by default, or you can use nil to keep the existing behavior (or don’t pass the setting at all to keep the existing behavior).

    Once you enable this feature, Swift will infer every object to have an @MainActor annotation unless you explicitly specify something else:

    @Observable
    class Person {
      var myValue: Int = 0
      let obj = TestClass()
    
      // This function will _always_ run on main 
      // if defaultIsolation is set to main actor
      func runMeSomewhere() async {
        MainActor.assertIsolated()
        // do some work, call async functions etc
      }
    }

    This code contains a nonisolated async function. This means that, by default, it would inherit the actor that we call runMeSomewhere from. If we call it from the main actor that’s where it runs. If we call it from another actor or from no actor, it runs away from the main actor.

    This probably wasn’t intended at all.

    Maybe we just wrote an async function so that we could call other functions that needed to be awaited. If runMeSomewhere doesn’t do any heavy processing, we probably want Person to be on the main actor. It’s an observable class so it probably drives our UI which means that pretty much all access to this object should be on the main actor anyway.

    With defaultIsolation set to MainActor.self, our Person gets an implicit @MainActor annotation so our Person runs all its work on the main actor.

    Let’s say we want to add a function to Person that’s not going to run on the main actor. We can use nonisolated just like we would otherwise:

    // This function will run on the caller's actor
    nonisolated func runMeSomewhere() async {
      MainActor.assertIsolated()
      // do some work, call async functions etc
    }

    And if we want to make sure we’re never on the main actor:

    // This function will run on the caller's actor
    @concurrent
    nonisolated func runMeSomewhere() async {
      MainActor.assertIsolated()
      // do some work, call async functions etc
    }

    We need to opt-out of this main actor inference for every function or property that we want to make nonisolated; we can’t do this for the entire type.

    Of course, your own actors will not suddenly start running on the main actor and types that you’ve annotated with your own global actors aren’t impacted by this change either.

    Should you opt-in to defaultIsolation?

    This is a tough question to answer. My initial thought is “yes”. For app targets, UI packages, and packages that mainly hold view models I definitely think that going main actor by default is the right choice.

    You can still introduce concurrency where needed and it will be much more intentional than it would have been otherwise.

    The fact that entire objects will be made main actor by default seems like something that might cause friction down the line but I feel like adding dedicated async packages would be the way to go here.

    The motivation for this option existing makes a lot of sense to me and I think I’ll want to try it out for a bit before making up my mind fully.



    Source link

    Concurrency Donny Exploring 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

    uikit – Why the title doesn’t follow the navigation inline state in iOS 26

    March 11, 2026

    ios – OS emoji keyboard causes UI freeze in chat TextField Flutter

    March 10, 2026

    Future Updates | Cocoanetics

    March 9, 2026
    Add A Comment
    Leave A Reply Cancel Reply

    Editors Picks

    Setting Up a Google Colab AI-Assisted Coding Environment That Actually Works

    March 11, 2026

    The economics of enterprise AI: What the Forrester TEI study reveals about Microsoft Foundry

    March 11, 2026

    The search for new bosons beyond Higgs – Physics World

    March 11, 2026

    Amazon is linking site hiccups to AI efforts

    March 11, 2026
    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!

    Setting Up a Google Colab AI-Assisted Coding Environment That Actually Works

    March 11, 2026

    The economics of enterprise AI: What the Forrester TEI study reveals about Microsoft Foundry

    March 11, 2026

    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
      © 2026 bigteetechhub.All Right Reserved

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