Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save DivineDominion/2e19f81e95c6f00b127d47e05752c4e1 to your computer and use it in GitHub Desktop.

Select an option

Save DivineDominion/2e19f81e95c6f00b127d47e05752c4e1 to your computer and use it in GitHub Desktop.
Final notes from https://christiantietze.de/posts/2025/processing-swift-concurrency-knowledge-with-zettelkasten/ -- showing how to use #Zettelkasten for programming

202509160703 Introducing actors to escape main thread isolation misuse

#actor #isolation #heuristic

In practice, programmers reach for stateless actors in Swift to express "this should never run on the main thread".[#20250916actor][]

That isn't good reasoning, because:

  • In practice ("runtime"), consider the overhead of using actors like network services[[202509160716]].
  • In principle, it doesn't satisfy any of the 3 necessary conditions for actor usage[[202509160821]]

To detect potentially premature actor usage, see the list of code smells:[[202509160833]]

For the purpose of getting off the main actor isolation, there are better tools:

  • async let introduces a suspension point for background work just as well, and is simpler[#20250916actor][]
  • with Swift 6.2 approachable concurrency, @concurrent function[[202509160705]] opts-in to run on the concurrent thread pool[#20250916actor][]

[#20250916actor]: Matthew Massicotte: "When should you use an actor?", 2025-09-06, https://www.massicotte.org/actors

202509160705 Run @concurrent in default MainActor isolation mode

#mainactor #concurrency #approachable-concurrency

Holly Borla's Swift 6.2 announcement post example for Swift Approachable Concurrency illustrates how regular functions and async calls will still be isolated on the main actor, but @concurrent allows scheduling on the global thread pool, which was the old default behavior[#swift62][]:

// In '-default-isolation MainActor' mode
struct Image {
  // The image cache is safe because it's protected
  // by the main actor.
  static var cachedImage: [URL: Image] = [:]

  static func create(from url: URL) async throws -> Image {
    if let image = cachedImage[url] {
      return image
    }

    let image = try await fetchImage(at: url)

    cachedImage[url] = image
    return image
  }

  // Fetch the data from the given URL and decode it.
  // This is performed on the concurrent thread pool to
  // keep the main actor free while decoding large images.
  @concurrent
  static func fetchImage(at url: URL) async throws -> Image {
    let (data, _) = try await URLSession.shared.data(from: url)
    return await decode(data: data)
  }
}

[#swift62]: Holly Borla: "Swift 6.2 Released", 2025-09-15, https://www.swift.org/blog/swift-6.2-released/

202509160716 Swift actor as network service metaphor

#swift #actor-isolation

Think of an actor like a remote network service:[#20250916actor][]

  • it's data is inaccessible to your process[#20250916actor][]
    • all communication goes through requests[#20250916actor][]
    • all data needs to be sent, cue Sendable[#20250916actor][]
  • callers can't know about other processes accessing the resource at the same time[#20250916actor][]

[#20250916actor]: Matthew Massicotte: "When should you use an actor?", 2025-09-06, https://www.massicotte.org/actors

202509160821 Necessary conditions for Swift actor usage

#actor #swift-concurrency

Necessary conditions to introduce an actor according to Matt Massicotte:[#20250916actor][]

  1. You have non-Sendable state.
  2. Operations that involve that state must be atomic.
  3. Those operations cannot be run on an existing actor.

Explicate the requirements in doc comments:

Every custom Swift actor needs justification in a comment doc that says “this is an actor because…” and the answer isn’t allowed to be “it helps deal with concurrency errors”.[#20250916actor][]

Needing to conform to Sendable protocols from other packages is not a necessary, but sufficient condition.[[202509160902]] The reason for this protocol's existence may be wrong, though, so try to change the requirement if you can.

[#20250916actor]: Matthew Massicotte: "When should you use an actor?", 2025-09-06, https://www.massicotte.org/actors

202509160833 Premature actor usage code smells

#actor #swift-concurrency #code-smell

Code smells that show Swift actor is being used prematurely:

  • The actor's state is Sendable.[[202509160821]]
  • There's only one function doing actual work -- that could just as well be owned by a MainActor-isolated object with opt-in @concurrent annotation to use the global thread pool[[202509160705]]
  • You made a protocol Sendable and now need actors, just because that makes usage simpler.[[202509160902]]

Getting off the main thread is not a sufficient reason, and there are better ways to implement that requirement.[[202509160703]]

202509160902 Sendable protocol conformance sufficient condition for Swift actor usage

#actor #sendable #protocol

Sendable protocols mark the whole conforming type as needing to be sendable.

Value types with sendable content can conform to this easily; but reference types with mutable state can't. You need to use actors for that for safe concurrent access.

When writing the actor implementation, check that necessary conditions for actor usage are met,[[202509160821]] and consider changing the API (if it's yours) or propose changes (if it's open source) to not make the whole protocol Sendable just to make the API implementation easier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment