# Swift Attributes and Annotations: @available, @MainActor, @Sendable

## Pro Summary

Attributes change compiler semantics; they aren't just documentation. `@available` gates declarations and `#available`/`#unavailable` gate call sites. `@MainActor` is static isolation, and Swift 6 also checks it at runtime when an isolated closure is invoked from code compiled without strict concurrency checking. `@Sendable` makes a closure's captures part of its type. Swift 6.2 moved the defaults: a module can make `MainActor` its default isolation (SE-0466), and with the `NonisolatedNonsendingByDefault` feature a `nonisolated async` function runs on the caller's actor, which makes `@concurrent` the explicit way to leave it (SE-0461).

```
@available(iOS 17, macOS 14, *)
@MainActor final class Model { var items: [String] = [] }

func run(_ job: @escaping @Sendable () async -> Void) { Task { await job() } }

@concurrent nonisolated func decode(_ data: Data) async throws -> [String] {
    try JSONDecoder().decode([String].self, from: data)
}

if #available(iOS 17, macOS 14, *) { /* new path */ } else { /* fallback */ }
```

Requires: `@MainActor` and global actors: Swift 5.5+ (SE-0316). `#unavailable`: Swift 5.6+. Runtime isolation checks: Swift 6 language mode (SE-0423). `@concurrent`, `nonisolated(nonsending)`, `isolated deinit`, and default `MainActor` isolation: Swift 6.2+.

Gotchas:

- A `@main` type's `static func main()` is implicitly `@MainActor`, so `await store.update()` on a `@MainActor` store from inside it involves no actor hop, and Swift 6 warns that no async operations occur in the `await`.
- In a module built with `-default-isolation MainActor` (SwiftPM `.defaultIsolation(MainActor.self)`), unannotated code is main-actor isolated. A synchronous free function reading `@MainActor` state compiles there but errors in a default module, and helpers meant for background work now need an explicit `nonisolated`.
- `@MainActor` on a class doesn't isolate its `deinit`. Calling an isolated method from `deinit` is a compile error unless you write `isolated deinit` (Swift 6.2, SE-0371).
- Region-based (`sending`) data-race diagnostics run after type checking, so `swiftc -typecheck` and other typecheck-only lint steps can pass code that a full build rejects.
- `@preconcurrency import` turns a module's Sendable diagnostics into warnings or hides them. It's a migration aid for un-annotated dependencies, not a fix, so remove it once the module adopts concurrency annotations.

Primary sources:

- [The Swift Programming Language: Attributes](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/attributes/)
- [SE-0423: Dynamic actor isolation enforcement from non-strict-concurrency contexts](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0423-dynamic-actor-isolation.md)
- [SE-0461: Run nonisolated async functions on the caller's actor by default](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0461-async-function-isolation.md)
- [SE-0466: Control default actor isolation inference](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0466-control-default-actor-isolation.md)

Source: https://devdocs10.com/articles/swift/swift-attributes-annotations  
By Murat Bo · Updated 2026-10-17 · DevDocs10 (https://devdocs10.com)

Swift attributes and annotations add extra meaning to declarations, types, functions, closures, and modules. They let you describe platform availability, compiler behavior, interoperability, concurrency rules, entry points, and performance hints without changing the core logic of your code. In this guide, you will learn what common Swift attributes do, how to write them correctly, when to use them, and which mistakes often cause confusing compiler errors.

> **Quick answer:** Swift attributes are metadata markers written with the `@` symbol, such as `@available`, `@MainActor`, and `@Sendable`. They do not replace normal Swift syntax; instead, they tell the compiler, runtime, or language tools how a declaration should behave or be interpreted.

> **Difficulty:** Intermediate
>
> **Helpful to know first:** basic Swift syntax, functions, types, access control, and the difference between compile-time checks and runtime behavior will make this topic much easier to understand.

## 1. What Is a Swift Attribute or Annotation?

A Swift attribute is a marker that starts with `@` and attaches extra information to a declaration or type. You place attributes before declarations such as structs, classes, enums, functions, properties, parameters, and closures.

Developers often say *attributes* and *annotations* interchangeably. In Swift documentation, *attribute* is the more precise term.

- `@available` describes which platforms or OS versions a declaration supports.
- `@MainActor` tells Swift that code must run on the main actor.
- `@Sendable` marks closures that can safely cross concurrency boundaries.
- `@main` defines the program entry point.
- `@objc` exposes declarations to the Objective-C runtime when needed.
- `@inline` gives the compiler inlining guidance.
- `@frozen` promises layout stability for certain public library types.

Some attributes are about source compatibility, some are about concurrency safety, and some are mainly for library authors or compiler optimization.

> Not every attribute is appropriate in everyday app code. For example, `@available` and `@MainActor` are common, while `@frozen` and `@inline` are more specialized.

## 2. Why Swift Attributes Matter

Attributes matter because they let you express rules that plain types and statements cannot express cleanly on their own.

- They help the compiler prevent invalid code, such as calling APIs on unsupported OS versions.
- They improve safety in modern concurrency by enforcing actor isolation and sendability.
- They make library APIs clearer by documenting intent directly in code.
- They help control interoperability and binary compatibility for frameworks.
- They can improve correctness and maintainability by moving important constraints closer to the declaration.

In real projects, these attributes are especially valuable when you are building reusable APIs, supporting multiple Apple platform versions, adopting async code, or exposing Swift code across module boundaries.

They are not decorations to add casually. Each attribute changes how the compiler reasons about your code, so adding one without understanding it can introduce unnecessary restrictions or hide design problems.

## 3. Basic Syntax

The basic idea is simple: place an attribute before the declaration it modifies.

### Single attribute on a function

This example marks a function as available only on newer iOS versions.

```swift
@available(iOS 15.0, *)
func refreshData() {
    print("Refreshing data")
}
```

The attribute sits above the function declaration and changes when the function may be used.

### Multiple attributes on one declaration

A declaration can have more than one attribute when each one serves a different purpose.

```swift
@MainActor
@available(iOS 15.0, *)
func updateInterface() {
    print("Updating UI-related state")
}
```

Here, the function is both main-actor isolated and version-limited.

### Attribute on a type

Attributes can also apply to an entire type.

```swift
@MainActor
final class SessionController {
    var status = "Idle"
}
```

All instance members of this type are treated as main-actor isolated unless you explicitly say otherwise.

### Attribute on a closure type

Some attributes apply to closure types rather than declarations.

```swift
let work: @Sendable () -> Void = {
    print("Running concurrent work")
}
```

This tells Swift the closure must be safe to transfer between concurrency domains.

## 4. Step-by-Step Examples

### Example 1: Using @available to protect version-specific APIs

Use `@available` when a declaration should only be used on certain OS versions. This is common when adopting APIs introduced in newer SDKs.

```swift
@available(iOS 16.0, macOS 13.0, *)
func useModernFeature() {
    print("Using a modern platform feature")
}

func runFeatureIfSupported() {
    if #available(iOS 16.0, macOS 13.0, *) {
        useModernFeature()
    } else {
        print("Fallback for older systems")
    }
}
```

The declaration attribute `@available` marks the function itself, while the statement form `#available` checks availability at runtime before calling it.

> This is an important comparison: `@available` belongs on declarations, while `#available` is used inside conditions.

### Example 2: Using @MainActor for UI-related state

`@MainActor` is used when code must execute on the main actor, which is especially important for UI-facing state and other main-thread-sensitive logic.

```swift
@MainActor
final class ProfileViewModel {
    var name = "Unknown"

    func rename(to newName: String) {
        name = newName
    }
}
```

Marking the whole type is often cleaner than marking each method individually. It communicates that the entire object is tied to the main actor.

### Example 3: Using @Sendable for concurrent closures

A closure that may run concurrently must avoid capturing non-sendable mutable state in unsafe ways. `@Sendable` tells Swift to enforce these rules more strictly.

```swift
func runTask(_ operation: @Sendable () -> Void) {
    operation()
}

let message = "Safe value capture"

runTask {
    print(message)
}
```

Output (tested with Swift 6.4):

```text
Safe value capture
```

Capturing an immutable string is fine because `String` is value-based and safe to transfer.

### Example 4: Using @main to define the entry point

`@main` marks the type that starts the program. This is common in command-line tools and app entry declarations.

```swift
@main
struct AppLauncher {
    static func main() {
        print("Application started")
    }
}
```

Output (tested with Swift 6.4):

```text
Application started
```

The compiler uses this type as the entry point instead of looking for older startup patterns.

### Example 5: Using @objc when Objective-C runtime access is required

`@objc` is mainly relevant when Swift declarations must be exposed to Objective-C runtime features, such as selector-based APIs.

```swift
import Foundation

class TimerHandler: NSObject {
    @objc func tick() {
        print("Timer fired")
    }
}
```

You should only use this when runtime exposure is actually needed. Most pure Swift code does not require it.

### Example 6: Using @inline as a compiler hint

`@inline` is an advanced optimization hint. It does not guarantee a specific machine-level result, but it can influence optimization decisions.

```swift
@inline(__always)
func square(_ value: Int) -> Int {
    value * value
}
```

This kind of attribute is usually for performance-sensitive code after measurement, not before.

## 5. Practical Use Cases

- Marking APIs that only exist on newer iOS, macOS, watchOS, or tvOS versions with `@available`.
- Protecting UI-related models, controllers, or state containers with `@MainActor`.
- Defining callback parameters for concurrent code using `@Sendable`.
- Declaring the startup type for a command-line app with `@main`.
- Exposing specific methods to runtime selector mechanisms with `@objc`.
- Publishing stable library enums for binary compatibility with `@frozen`.
- Adding measured optimization hints to small hot-path functions with `@inline`.

## 6. Common Mistakes

### Mistake 1: Using @available but forgetting the runtime check

It is common to mark a declaration as version-limited and then call it unconditionally from code that still runs on older systems.

**Problem:** The compiler can reject the call because the API may not exist on the deployment target. The declaration is annotated correctly, but the call site is still unsafe.

```swift
@available(iOS 16.0, *)
func newFeature() {
    print("New feature")
}

func start() {
    newFeature()
}
```

**Fix:** Guard the call with `#available` when the current runtime might be older than the API requirement.

```swift
@available(iOS 16.0, *)
func newFeature() {
    print("New feature")
}

func start() {
    if #available(iOS 16.0, *) {
        newFeature()
    } else {
        print("Fallback path")
    }
}
```

The corrected version works because the newer API is only called when the current system supports it.

### Mistake 2: Accessing @MainActor state from a nonisolated context incorrectly

Beginners often assume `@MainActor` is just documentation, but it is enforced by Swift concurrency rules.

**Problem:** Accessing main-actor-isolated members from another concurrency context can produce isolation errors such as actor-related compile-time diagnostics.

```swift
@MainActor
final class Counter {
    var value = 0
}

func printValue(_ counter: Counter) {
    print(counter.value)
}
```

**Fix:** Access the isolated member from a main-actor context or use an async hop when appropriate.

```swift
@MainActor
final class Counter {
    var value = 0
}

@MainActor
func printValue(_ counter: Counter) {
    print(counter.value)
}
```

The corrected version works because both the type and the function are isolated to the same actor.

### Mistake 3: Capturing unsafe mutable state in an @Sendable closure

`@Sendable` closures are stricter than ordinary closures. They cannot freely capture mutable reference-based state that may be unsafe across concurrency boundaries.

**Problem:** This can trigger sendability diagnostics when Swift detects that the closure captures non-sendable or unsafely shared state.

```swift
final class LogStore {
    var entries: [String] = []
}

let store = LogStore()

let job: @Sendable () -> Void = {
    store.entries.append("New item")
}
```

**Fix:** Capture sendable values, redesign shared state, or isolate the mutable state behind an actor.

```swift
actor LogStore {
    var entries: [String] = []

    func add(_ entry: String) {
        entries.append(entry)
    }
}
```

The corrected approach works because actor isolation protects the mutable shared state.

### Mistake 4: Adding @inline(__always) without measuring performance

Developers sometimes assume forcing inline behavior always makes code faster.

**Problem:** Overusing inlining hints can increase binary size and may even reduce performance depending on optimization context.

```swift
@inline(__always)
func formatMessage(_ name: String) -> String {
    "Hello, \(name)"
}
```

**Fix:** Let the optimizer decide unless profiling shows a real hot-path benefit.

```swift
func formatMessage(_ name: String) -> String {
    "Hello, \(name)"
}
```

The corrected version works because it avoids unnecessary optimization hints and keeps performance tuning evidence-based.

## 7. Best Practices

### Practice 1: Use the narrowest attribute scope that expresses your intent

If only one method needs version gating or runtime exposure, annotate that method rather than the entire type.

```swift
// Less preferred when only one member needs the attribute
@MainActor
final class ReportBuilder {
    func build() -> String {
        "report"
    }

    func publishUIStatus() {
        print("Published")
    }
}
```

```swift
// Preferred when only one method is UI-related
final class ReportBuilder {
    func build() -> String {
        "report"
    }

    @MainActor
    func publishUIStatus() {
        print("Published")
    }
}
```

This keeps isolation and constraints focused on the code that actually needs them.

### Practice 2: Prefer @available for declarations and #available for call-site branching

These two forms solve related but different problems. Use the declaration attribute to describe support, and the conditional check to decide what to run at runtime.

```swift
@available(macOS 13.0, *)
func modernPath() {
    print("Modern path")
}

func startWork() {
    if #available(macOS 13.0, *) {
        modernPath()
    } else {
        print("Legacy path")
    }
}
```

This pattern is clear, safe, and easy for other developers to read.

### Practice 3: Use @Sendable as an API contract, not just a compiler appeasement tool

If a closure can be executed concurrently, mark it `@Sendable` in the function signature so callers know the rules.

```swift
// Less explicit
func performWork(_ job: () -> Void) {
    job()
}

// Preferred for concurrent-capable APIs
func performWork(_ job: @Sendable () -> Void) {
    job()
}
```

This makes concurrency expectations part of the API instead of a hidden implementation detail.

### Practice 4: Be cautious with library-facing attributes such as @frozen

`@frozen` is a promise to clients of a public library. Once made, it limits how you can evolve the type.

```swift
// Public library design choice
@frozen
public enum Priority {
    case low
    case medium
    case high
}
```

Use this only when you are confident the public shape is stable enough for long-term compatibility.

## 8. Limitations and Edge Cases

- `@available` does not automatically supply fallback behavior. You still need alternate code paths for older systems.
- `@MainActor` means actor isolation, not merely a comment that something should probably be on the main thread.
- `@Sendable` may surface warnings or errors only after you enable stricter concurrency checking or move code into more concurrent contexts.
- `@objc` is limited by what can be represented in the Objective-C runtime model. Not all Swift features map cleanly.
- `@inline` is only a hint and may not behave exactly how you expect across optimization levels.
- `@frozen` is primarily relevant for resilient public libraries, not ordinary internal app code.
- `@main` can only identify one valid entry point in a target. Multiple competing entry points will cause build errors.

> A useful mental model is that some attributes affect *who can call code*, some affect *where code can run*, and others affect *how the compiler treats code*.

## 9. Practical Mini Project

This small example combines several attributes in one realistic command-line style program. It uses `@main` for the entry point, `@available` for a newer feature, `@MainActor` for UI-like state isolation, and `@Sendable` for a closure contract.

```swift
import Foundation

@MainActor
final class StatusStore {
    private(set) var status = "Starting"

    func update(_ newStatus: String) {
        status = newStatus
        print("Status: \(status)")
    }
}

func performBackgroundWork(_ job: @Sendable () -> String) -> String {
    job()
}

@available(macOS 13.0, *)
func modernSummary() -> String {
    "Modern summary feature enabled"
}

@main
struct Program {
    static func main() {
        let store = StatusStore()
        let result = performBackgroundWork {
            "Background work finished"
        }

        store.update(result)

        if #available(macOS 13.0, *) {
            print(modernSummary())
        } else {
            print("Legacy summary feature")
        }
    }
}
```

Output (tested with Swift 6.4):

```text
Status: Background work finished
Modern summary feature enabled
```

This example shows how attributes can work together rather than in isolation. The program has a clear entry point, a concurrency-safe closure contract, main-actor-isolated state updates, and a runtime availability check for a newer feature. Because `main()` in a `@main` type runs on the main actor, it can call the `@MainActor` store directly without `await`; code running outside the main actor would need `await store.update(result)`.

## 10. Key Points

- Swift attributes are metadata markers that begin with `@` and modify how declarations are interpreted.
- `@available` describes platform support, while `#available` checks support at runtime.
- `@MainActor` enforces main-actor isolation for code that must run in a main-thread-related context.
- `@Sendable` makes closure concurrency requirements explicit and safer.
- `@main` defines the target entry point.
- `@objc`, `@frozen`, and `@inline` are more specialized and should be used intentionally.
- Good attribute usage improves safety, API clarity, and maintainability.

## 11. Practice Exercise

Build a small Swift program that practices three different attributes together.

- Create a type named `MessageStore` marked with `@MainActor`.
- Add a method that updates a stored message string.
- Create a function that accepts an `@Sendable` closure returning a `String`.
- Create another function marked with `@available` for macOS 13.0 or later.
- From `@main`, call the closure-based function, update the store, and conditionally call the version-limited function with `#available`.

**Expected output:** A printed message from the closure result, followed by either the modern feature message or a fallback message.

**Hint:** Keep the closure capture simple by returning a string literal or an immutable constant.

```swift
import Foundation

@MainActor
final class MessageStore {
    private(set) var message = "Initial"

    func update(_ newMessage: String) {
        message = newMessage
        print("Stored: \(message)")
    }
}

func makeMessage(_ builder: @Sendable () -> String) -> String {
    builder()
}

@available(macOS 13.0, *)
func modernFeatureMessage() {
    print("Modern feature is available")
}

@main
struct Runner {
    static func main() {
        let store = MessageStore()
        let text = makeMessage {
            "Hello from a sendable closure"
        }

        store.update(text)

        if #available(macOS 13.0, *) {
            modernFeatureMessage()
        } else {
            print("Using fallback behavior")
        }
    }
}
```

Output (tested with Swift 6.4):

```text
Stored: Hello from a sendable closure
Modern feature is available
```

## 12. Final Summary

Swift attributes and annotations are a powerful part of the language because they let you express information that ordinary syntax cannot express clearly enough on its own. With them, you can describe availability, actor isolation, concurrency safety, entry points, compiler hints, and library evolution rules directly on the declarations they affect.

For most developers, the most important attributes to understand first are `@available`, `@MainActor`, `@Sendable`, and `@main`. More advanced attributes such as `@frozen`, `@inline`, and `@objc` become more relevant when you work on frameworks, optimization-sensitive code, or interoperability boundaries.

Your best next step is to review a real Swift project and identify where attributes are already in use. Then practice rewriting a few declarations with the correct attribute and call-site checks so the compiler can enforce more of your design for you.

## Where AI Coding Assistants Get This Wrong

- Often suggested: Passes a closure written inside a `@MainActor` type to a completion-handler API that calls back on a background queue (an un-annotated SDK, C, or Objective-C callback, or a module built without strict concurrency checking), assuming it's safe because it compiles.
  Instead: Mark the closure `@Sendable` (or `nonisolated`), hop back explicitly with `Task { @MainActor in ... }`, or call the API's async variant if one exists. (Swift 6 infers the closure as `@MainActor` and inserts a runtime isolation check (SE-0423), so the off-main callback crashes in `dispatch_assert_queue` instead of racing silently.)
- Often suggested: Assumes a `nonisolated async` function always runs on the global concurrent executor and uses one to move heavy work off the main actor.
  Instead: Mark functions that must leave the caller's actor `@concurrent` (Swift 6.2+), or use `Task.detached`, and check whether the module enables the `NonisolatedNonsendingByDefault` upcoming feature. (Under SE-0461 semantics, a `nonisolated async` function inherits the caller's actor, so a call from the main actor runs on the main thread.)
- Often suggested: Silences Swift 6 errors on its own types with `@unchecked Sendable`, `nonisolated(unsafe)`, or `@preconcurrency`.
  Instead: Make the type genuinely `Sendable` (`final`, with immutable `let` properties of Sendable types), turn it into an `actor`, or protect its state with `Mutex` from `Synchronization` (macOS 15 / iOS 18), which lets a `final class` conform to plain `Sendable`. (Those escape hatches switch off checking for the declaration, so data races become silent runtime bugs instead of compile errors.)
