The nonisolated and the isolated keywords in Swift


Greetings, traveler!

Swift actor isolation is a Swift Concurrency feature that allows actors to restrict concurrent access to their state. SE-313 introduced two keywords to handle actor isolation: isolated and nonisolated.

By the way, you can read more about actors and Swift Concurrency to delve deeper into this topic.

isolated

By default, all methods and properties of an actor are isolated to that actor. What does that mean? Consider this code example: we have a Wallet (an actor). It has one property called ‘balance’. Our task is to print the wallet balance in the console.

actor Wallet {
    var balance: Double = .zero
}

func showBalance(_ wallet: Wallet) {
    print(wallet.balance)
}

Can you guess what’s wrong with this code? The thing is that the ‘balance’ property is actor-isolated. The compiler will tell us that:

Actor-isolated property ‘balance’ can not be referenced from a non-isolated context.

So, we can repair our code by adding the ‘await’ keyword and marking this function with the ‘async’ keyword.

func showBalance(_ wallet: Wallet) async {
    await print(wallet.balance)
}

Everything is working well now. However, we can directly access desired data by marking our function parameter with the ‘isolated’ keyword.

func showBalance(_ wallet: isolated Wallet) {
    print(wallet.balance)
}

Moreover, this move won’t break the actor’s feature — it will still be accessible only by one thread at a time. Since then, every time we call this method, we need to use the ‘await’ keyword.

func displayData() {
    Task {
        let wallet = Wallet()
        await showBalance(wallet)
    }
}

nonisolated

Consider this situation: We need to make our Wallet apply the CustomStringConvertible protocol. First, we will add the new property ‘name’, which will be a let constant.

actor Wallet {
        
    let name: String
    
    var balance: Double = .zero
    
    init(name: String) {
        self.name = name
    }
    
}

Second, we will create an extension for protocol conformance. Sure thing, we will get an error.

extension Wallet: CustomStringConvertible {
    var description: String {
        name // ❌ Compile error: Actor-isolated property 'description' cannot be used to satisfy nonisolated protocol requirement
    }
}

We can add the ‘nonisolated’ keyword to our ‘description’ property to fix it.

extension Wallet: CustomStringConvertible {
    nonisolated var description: String {
        name
    }
}

But can we add a ‘balance’ variable value to the wallet’s description? Since ‘balance’ is a mutable property, its changes can affect the actor’s state. The compiler doesn’t allow us to do this.

extension Wallet: CustomStringConvertible {
    nonisolated var description: String {
        name + String(balance) // ❌ Compile error: Actor-isolated property 'balance' can not be referenced from a non-isolated context
    }
}

Conclusion

We explored a convenient tool for handling actors’ isolation. That’s cool! Now, let’s move to the following article, which will discuss Global Actors.