Actor — what it is, data isolation and concurrency

Author: IT Sectr Published: 2026-06-18 Reading time: 11 min

Actor is a type introduced in Swift 5.5+ that solves the problem of data races at the language level. Unlike manual locks and DispatchQueue queues, an actor automatically isolates its state and synchronizes access to it. This means that two threads cannot simultaneously modify the same property of an actor type, eliminating race conditions without additional effort from the developer. The mechanism is based on the concept of actor isolation, where the compiler tracks access to actor properties and methods. According to Apple, 2025, the actor model is a fundamental part of Swift's concurrency system.

Key Takeaways

  • Actor is a type that automatically isolates its state from data races at the compiler level
  • Isolation guarantees that only one thread accesses the actor instance at a time
  • Calls to actor methods execute asynchronously via the async/await mechanism
  • Nonisolated allows selectively disabling isolation for individual actor members
  • Reentrancy permits re-entry into an actor, preventing deadlocks

What is Actor in Swift?

Actor is a reference type, similar to a class, but with automatic protection against data races. It was introduced in Swift 5.5 as part of the concurrency system alongside async/await and Task. An actor guarantees that its mutable state will never be simultaneously read or written from different threads without explicit synchronization.

Actor Declaration Syntax

To declare an actor, use the actor keyword followed by curly braces with its members. Syntactically, an actor resembles a class, but its behavior differs radically.

swift
actor BankAccount {
    private var balance: Double
    
    init(initialBalance: Double) {
        self.balance = initialBalance
    }
    
    func deposit(amount: Double) {
        balance += amount
    }
    
    func getBalance() -> Double {
        return balance
    }
}

The compiler automatically isolates all actor properties and methods so they are only accessible within the actor context. When trying to access balance from outside the actor, the compiler will produce an error if the call is not marked as async.

How Data Isolation Works in Actor

Data isolation is the key concept of an actor. An actor guarantees mutually exclusive access to its state through the actor executor mechanism. Each actor has its own executor that processes all accesses to its isolated members sequentially.

How Isolation Works

When code outside an actor calls its method, the call is placed in the actor executor queue. The actor executes only one task at a time, guaranteeing the absence of races. If two threads call deposit simultaneously, the second call waits for the first to complete.

swift
let account = BankAccount(initialBalance: 1000.0)
// Async call — required from outside actor
await account.deposit(amount: 500.0)
let currentBalance = await account.getBalance()

Every call to an actor method requires await because the actor may be busy with another task. This is not a bug but a conscious design that prevents races. Swift implicitly makes actor property getters and setters async, so reading a property also requires await.

Key Differences Between Actor and Class

Actor and class are both reference types, but their behavior in a multithreaded environment differs dramatically. A class does not provide any automatic protection against races, whereas an actor embeds it at the compiler level through the type system.

CharacteristicActorClass
Race protectionAutomatic, at the compiler levelRequires manual synchronization
Property accessOnly via await from outsideDirect, without synchronization
InheritanceOnly from other actorsStandard class-based
Protocol conformanceCan conform to protocolsStandard
PerformanceLow overhead with isolationFaster without synchronization

Actor Inheritance

An actor can only inherit from another actor and cannot inherit from a class. This is intentional because a class lacks the actor isolation mechanism, and mixing types would break safety guarantees.

swift
actor SavingsAccount: BankAccount {
    func applyInterest(rate: Double) {
        let interest = balance * rate
        balance += interest
    }
}

Asynchronous Calls to Actor Methods

Asynchronous calls are the mechanism for interacting with an actor from external code. Since an actor isolates its state, any access to its members from outside requires await. This allows Swift to guarantee that the calling code does not block the thread and the actor can process other requests.

Global and Local Actors

In addition to declared actor types, Swift supports global actors — the @MainActor attribute, which marks classes, properties, or methods as executable on the main thread. This is especially useful for UI code in iOS applications.

swift
@MainActor
class ViewModel: ObservableObject {
    @Published var title: String = ""
    
    func updateTitle() {
        // This code is guaranteed to run on the main thread
        title = "New Title"
    }
}

Using @MainActor eliminates the need to manually call DispatchQueue.main.async, making the code cleaner and safer. The compiler ensures that switching to the main thread happens correctly.

Nonisolated and Actor-Isolated Contexts

Nonisolated is a keyword that allows marking a method or computed property of an actor as not isolated. Such members do not have access to the actor-isolated state but can be called without await from outside the actor.

Nonisolated Functions

Nonisolated methods are useful for computations that do not depend on the actor's mutable state. For example, the formatBalance method does not access balance directly but only formats the passed value — such a method is safe to make nonisolated.

swift
actor BankAccount {
    private var balance: Double = 0
    
    nonisolated func formatBalance(amount: Double) -> String {
        return "$\(amount)"
    }
}

Nonisolated members execute synchronously and do not require await. However, they cannot read actor-isolated properties directly. If a nonisolated method needs a value from the actor, it must be passed as a parameter.

Reentrancy and Re-entry

Reentrancy is a mechanism that allows re-entry into an actor while waiting for an asynchronous call. Without reentrancy, an actor could deadlock forever if one of its methods waited for another which in turn waited for the first.

How Reentrancy Works

When code inside an actor executes await, the actor suspends the current task and can process another queued task. After await completes, the task resumes. This prevents deadlocks but requires caution: the actor state between await points may change.

swift
actor DataProcessor {
    var cache: [Int: String] = [:]
    
    func process(id: Int) async -> String {
        if let cached = cache[id] {
            return cached
        }
        // await — reentrancy point
        let result = await fetchData(id: id)
        // Cache may have changed after await — recheck
        cache[id] = result
        return result
    }
}

Developers must account for reentrancy and check the actor state after await points. A typical mistake is assuming that actor isolation persists across asynchronous suspension points. In practice, between await and the next statement, the state may differ from what was expected.

Frequently Asked Questions

How is Actor different from a class in Swift?

Actor automatically isolates its state from data races, requiring await for external access. A class does not provide such protection — the developer is responsible for synchronization via locks or queues. An actor only inherits from an actor, a class from a class.

Can Actor be subclassed?

Yes, an actor can inherit from another actor. The subclass gets all isolated properties and methods of the parent. An actor cannot inherit from a class because classes lack the actor isolation mechanism at the compiler level.

What is an actor-isolated context?

An actor-isolated context is a code area where direct access to the actor's mutable state is allowed. Inside actor methods marked as isolated (by default), you can read and write properties without await. The compiler checks isolation boundaries.

How to pass data from an Actor to external code?

Data from an actor is passed through async methods that return Sendable types, or through nonisolated methods that accept values as parameters. You can also create an async property that returns a snapshot of the actor's state as a Sendable structure.

Does Actor support protocol conformance?

Yes, an actor can conform to protocols. If a protocol contains isolated requirements (actor-isolated), they automatically become actor-isolated. For async methods in protocols, you can specify that they must be called on a specific actor using the isolated marker.

Summary

  • Actor is a reference type with automatic data isolation, introduced in Swift 5.5+
  • Isolation works through an actor executor that processes requests sequentially
  • External access to an actor requires await — this guarantees freedom from races
  • Nonisolated allows declaring synchronous methods that do not access isolated state
  • Global actors (@MainActor) extend isolation to the entire execution thread
  • Reentrancy prevents deadlocks by allowing re-entry into an actor during await

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also