axiom-swift-concurrency by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swift-concurrency目的:从单线程到并发 Swift 代码的渐进式旅程 Swift 版本:Swift 6.0+,Swift 6.2+ 支持 @concurrent iOS 版本:iOS 17+(iOS 18.2+ 支持 @concurrent) Xcode:Xcode 16+(Xcode 16.2+ 支持 @concurrent) 背景:WWDC 2025-268 "拥抱 Swift 并发" - 实现数据竞争安全的可行路径
✅ 在以下情况使用此技能:
@MainActor 类或异步函数时@MainActor、nonisolated、@concurrent 或 actor 隔离之间做选择时广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
Sendable 时❌ 不要将此技能用于:
axiom-swiftui-debugging 或 axiom-swiftui-performance)苹果指南(WWDC 2025-268):"你的应用应该从在主线程上运行所有代码开始,仅使用单线程代码你就能走得很远。"
Single-Threaded → Asynchronous → Concurrent → Actors
↓ ↓ ↓ ↓
Start here Hide latency Background Move data
(network) CPU work off main
何时推进:
关键见解:并发代码更复杂。仅在性能分析表明需要时才引入并发。
在 Swift 6 中,默认所有代码都在主线程上运行。
// ✅ 简单,单线程
class ImageModel {
var imageCache: [URL: Image] = [:]
func fetchAndDisplayImage(url: URL) throws {
let data = try Data(contentsOf: url) // 读取本地文件
let image = decodeImage(data)
view.displayImage(image)
}
func decodeImage(_ data: Data) -> Image {
// 解码图像数据
return Image()
}
}
主 Actor 模式(Xcode 26+):
@MainActor 保护构建设置(Xcode 26+):
Build Settings → Swift Compiler — Language
→ "Default Actor Isolation" = Main Actor
Build Settings → Swift Compiler — Upcoming Features
→ "Approachable Concurrency" = Yes
何时足够:如果所有操作都很快(<16ms 以实现 60fps),保持单线程!
当等待数据(网络、文件 I/O)会冻结 UI 时,添加 async/await。
// ❌ 阻塞主线程直到网络完成
func fetchAndDisplayImage(url: URL) throws {
let (data, _) = try URLSession.shared.data(from: url) // ❌ 冻结 UI!
let image = decodeImage(data)
view.displayImage(image)
}
// ✅ 挂起而不阻塞主线程
func fetchAndDisplayImage(url: URL) async throws {
let (data, _) = try await URLSession.shared.data(from: url) // ✅ 在此挂起
let image = decodeImage(data) // ✅ 数据到达时在此恢复
view.displayImage(image)
}
发生了什么:
await 挂起函数而不阻塞主线程响应用户事件创建任务:
class ImageModel {
var url: URL = URL(string: "https://swift.org")!
func onTapEvent() {
Task { // ✅ 为用户操作创建任务
do {
try await fetchAndDisplayImage(url: url)
} catch {
displayError(error)
}
}
}
}
多个异步任务可以通过轮换在同一线程上运行:
Task 1: [Fetch Image] → (suspend) → [Decode] → [Display]
Task 2: [Fetch News] → (suspend) → [Display News]
Main Thread Timeline:
[Fetch Image] → [Fetch News] → [Decode Image] → [Display Image] → [Display News]
好处:
何时使用任务:
当CPU 密集型工作阻塞 UI 时,添加并发。
性能分析显示 decodeImage() 耗时 200ms,导致 UI 卡顿:
func fetchAndDisplayImage(url: URL) async throws {
let (data, _) = try await URLSession.shared.data(from: url)
let image = decodeImage(data) // ❌ 在主线程上耗时 200ms!
view.displayImage(image)
}
@concurrent 属性(Swift 6.2+)强制函数始终在后台线程上运行:
func fetchAndDisplayImage(url: URL) async throws {
let (data, _) = try await URLSession.shared.data(from: url)
let image = await decodeImage(data) // ✅ 在后台线程上运行
view.displayImage(image)
}
@concurrent
func decodeImage(_ data: Data) async -> Image {
// ✅ 始终在后台线程池上运行
// 适用于:图像处理、文件 I/O、解析
return Image()
}
@concurrent 的作用:
await 无法访问 @MainActor 属性要求:Swift 6.2,Xcode 16.2+,iOS 18.2+
nonisolated(库 API)如果提供通用 API,使用 nonisolated 代替:
// ✅ 保持在调用者的 actor 上
nonisolated
func decodeImage(_ data: Data) -> Image {
// 在调用它的任何 actor 上运行
// 主 actor → 保持在主 actor 上
// 后台 → 保持在后台
return Image()
}
何时使用 nonisolated:
何时使用 @concurrent:
当你标记一个函数为 @concurrent 时,编译器会显示主 actor 访问:
@MainActor
class ImageModel {
var cachedImage: [URL: Image] = [:] // 主 actor 数据
@concurrent
func decodeImage(_ data: Data, at url: URL) async -> Image {
if let image = cachedImage[url] { // ❌ 错误:主 actor 访问!
return image
}
// decode...
}
}
策略 1:移动到调用者(保持工作同步):
func fetchAndDisplayImage(url: URL) async throws {
// ✅ 在异步工作之前在主 actor 上检查缓存
if let image = cachedImage[url] {
view.displayImage(image)
return
}
let (data, _) = try await URLSession.shared.data(from: url)
let image = await decodeImage(data) // 现在不需要 URL
view.displayImage(image)
}
@concurrent
func decodeImage(_ data: Data) async -> Image {
// ✅ 不需要主 actor 访问
return Image()
}
策略 2:使用 await(异步访问主 actor):
@concurrent
func decodeImage(_ data: Data, at url: URL) async -> Image {
// ✅ 使用 await 访问主 actor 数据
if let image = await cachedImage[url] {
return image
}
// decode...
}
策略 3:设为 nonisolated(如果不需要 actor):
nonisolated
func decodeImage(_ data: Data) -> Image {
// ✅ 无 actor 隔离,可以从任何地方调用
return Image()
}
当工作在后台运行时:
Main Thread: [UI] → (suspend) → [UI Update]
↓
Background Pool: [Task A] → [Task B] → [Task A resumes]
Thread 1 Thread 2 Thread 3
关键点:
当过多代码在主 actor 上运行导致争用时,添加 actors。
@MainActor
class ImageModel {
var cachedImage: [URL: Image] = [:]
let networkManager: NetworkManager = NetworkManager() // ❌ 也是 @MainActor
func fetchAndDisplayImage(url: URL) async throws {
// ✅ 后台工作...
let connection = await networkManager.openConnection(for: url) // ❌ 跳转到主线程!
let data = try await connection.data(from: url)
await networkManager.closeConnection(connection, for: url) // ❌ 跳转到主线程!
let image = await decodeImage(data)
view.displayImage(image)
}
}
问题:后台任务不断跳转到主 actor 以访问网络管理器。
// ✅ 将网络状态移出主 actor
actor NetworkManager {
var openConnections: [URL: Connection] = [:]
func openConnection(for url: URL) -> Connection {
if let connection = openConnections[url] {
return connection
}
let connection = Connection()
openConnections[url] = connection
return connection
}
func closeConnection(_ connection: Connection, for url: URL) {
openConnections.removeValue(forKey: url)
}
}
@MainActor
class ImageModel {
let networkManager: NetworkManager = NetworkManager()
func fetchAndDisplayImage(url: URL) async throws {
// ✅ 现在主要在后台运行
let connection = await networkManager.openConnection(for: url)
let data = try await connection.data(from: url)
await networkManager.closeConnection(connection, for: url)
let image = await decodeImage(data)
view.displayImage(image)
}
}
改变了什么:
NetworkManager 现在是一个 actor 而不是 @MainActor class✅ 将 actors 用于:
❌ 不要将 actors 用于:
@MainActor@MainActor 或非 Sendable指南:先进行性能分析。如果主 actor 有太多状态导致瓶颈,一次提取一个子系统到 actors 中。
当数据在 actors 或任务之间传递时,Swift 会检查它是否是 Sendable(可安全共享)。
// ✅ 值类型在传递时被复制
let url = URL(string: "https://swift.org")!
Task {
// ✅ 这是 url 的副本,不是原始值
// URLSession.shared.data 自动在后台运行
let data = try await URLSession.shared.data(from: url)
}
// ✅ 原始 url 未被后台任务更改
为什么安全:每个 actor 获得其自己独立的副本。更改不会影响其他副本。
// ✅ 基本类型
extension URL: Sendable {}
extension String: Sendable {}
extension Int: Sendable {}
extension Date: Sendable {}
// ✅ Sendable 元素的集合
extension Array: Sendable where Element: Sendable {}
extension Dictionary: Sendable where Key: Sendable, Value: Sendable {}
// ✅ 具有 Sendable 存储的结构体/枚举
struct Track: Sendable {
let id: String
let title: String
let duration: TimeInterval
}
enum PlaybackState: Sendable {
case stopped
case playing
case paused
}
// ✅ 主 actor 类型
@MainActor class ImageModel {} // 隐式 Sendable(actor 保护状态)
// ✅ Actor 类型
actor NetworkManager {} // 隐式 Sendable(actor 保护状态)
// ❌ 类默认不是 Sendable
class MyImage {
var width: Int
var height: Int
var pixels: [Color]
func scale(by factor: Double) {
// 修改共享状态
}
}
let image = MyImage()
let otherImage = image // ✅ 两者引用同一个对象
image.scale(by: 0.5) // ✅ 更改通过 otherImage 可见!
并发问题:
func scaleAndDisplay(imageName: String) {
let image = loadImage(imageName)
Task {
image.scale(by: 0.5) // 后台任务修改
}
view.displayImage(image) // 主线程读取
// ❌ 数据竞争!两个线程可能接触同一个对象!
}
解决方案 1:在发送前完成修改:
@concurrent
func scaleAndDisplay(imageName: String) async {
let image = loadImage(imageName)
image.scale(by: 0.5) // ✅ 所有修改都在后台
image.applyAnotherEffect() // ✅ 仍在后台
await view.displayImage(image) // ✅ 修改完成后发送到主 actor
// ✅ 主 actor 现在独占拥有 image
}
解决方案 2:不要并发共享类:
保持模型类为 @MainActor 或非 Sendable 以防止并发访问。
在以下情况下自动发生:
将数据传入/传出 actors
将数据传入/传出任务
使用 await 跨 actor 边界
func fetchAndDisplayImage(url: URL) async throws { let (data, _) = try await URLSession.shared.data(from: url) // ↑ Sendable ↑ Sendable(跨到后台)
let image = await decodeImage(data)
// ↑ data 跨到后台(必须是 Sendable)
// ↑ image 返回到主线程(必须是 Sendable)
}
何时:类型跨 actor 边界
// ✅ 枚举(无关联值)
private enum PlaybackState: Sendable {
case stopped
case playing
case paused
}
// ✅ 结构体(所有属性都是 Sendable)
struct Track: Sendable {
let id: String
let title: String
let artist: String?
}
// ✅ 具有 Sendable 关联值的枚举
enum Result: Sendable {
case success(data: Data)
case failure(error: Error) // Error 是 Sendable
}
何时:nonisolated 委托方法需要更新 @MainActor 状态
nonisolated func delegate(_ param: SomeType) {
// ✅ 步骤 1:在 Task 之前捕获委托参数值
let value = param.value
let status = param.status
// ✅ 步骤 2:Task 跳转到 MainActor
Task { @MainActor in
// ✅ 步骤 3:安全访问 self(我们在 MainActor 上)
self.property = value
print("Status: \(status)")
}
}
为什么:委托方法是 nonisolated(从库的线程调用)。在 Task 之前捕获参数。在 Task { @MainActor in } 内部访问 self 是安全的。
何时:任务存储为属性或运行时间很长
class MusicPlayer {
private var progressTask: Task<Void, Never>?
func startMonitoring() {
progressTask = Task { [weak self] in // ✅ 弱捕获
guard let self = self else { return }
while !Task.isCancelled {
await self.updateProgress()
}
}
}
deinit {
progressTask?.cancel()
}
}
注意:短暂存在的任务(未存储)可以使用强捕获。
何时:CPU 密集型工作应始终在后台运行(Swift 6.2+)
@concurrent
func decodeImage(_ data: Data) async -> Image {
// ✅ 始终在后台线程池上运行
// 适用于:图像处理、文件 I/O、JSON 解析
return Image()
}
// 用法
let image = await decodeImage(data) // 自动卸载
要求:Swift 6.2,Xcode 16.2+,iOS 18.2+
何时:类型需要以特定 actor 隔离方式符合协议
protocol Exportable {
func export()
}
class PhotoProcessor {
@MainActor
func exportAsPNG() {
// 需要 UI 访问的导出逻辑
}
}
// ✅ 使用显式隔离符合
extension StickerModel: @MainActor Exportable {
func export() {
photoProcessor.exportAsPNG() // ✅ 安全:两者都在 MainActor 上
}
}
何时使用:协议方法需要特定的 actor 上下文(UI 用主 actor,处理用后台)
何时:读取可能在访问过程中更改的多个属性
var currentTime: TimeInterval {
get async {
// ✅ 缓存引用以获取原子快照
guard let player = player else { return 0 }
return player.currentTime
}
}
何时:代码接触 UI
@MainActor
class PlayerViewModel: ObservableObject {
@Published var currentTrack: Track?
@Published var isPlaying: Bool = false
func play(_ track: Track) async {
// 已经在 MainActor 上
self.currentTrack = track
self.isPlaying = true
}
}
actor DataFetcher {
let modelContainer: ModelContainer
func fetchAllTracks() async throws -> [Track] {
let context = ModelContext(modelContainer)
let descriptor = FetchDescriptor<Track>(
sortBy: [SortDescriptor(\.title)]
)
return try context.fetch(descriptor)
}
}
@MainActor
class TrackViewModel: ObservableObject {
@Published var tracks: [Track] = []
func loadTracks() async {
let fetchedTracks = try await fetcher.fetchAllTracks()
self.tracks = fetchedTracks // 回到 MainActor
}
}
actor CoreDataFetcher {
func fetchTracksID(genre: String) async throws -> [String] {
let context = persistentContainer.newBackgroundContext()
var trackIDs: [String] = []
try await context.perform {
let request = NSFetchRequest<CDTrack>(entityName: "Track")
request.predicate = NSPredicate(format: "genre = %@", genre)
let results = try context.fetch(request)
trackIDs = results.map { $0.id } // 在离开上下文之前提取 ID
}
return trackIDs // 轻量级,Sendable
}
}
actor DataImporter {
func importRecords(_ records: [RawRecord], onProgress: @MainActor (Int, Int) -> Void) async throws {
let chunkSize = 1000
let context = ModelContext(modelContainer)
for (index, chunk) in records.chunked(into: chunkSize).enumerated() {
for record in chunk {
context.insert(Track(from: record))
}
try context.save()
let processed = (index + 1) * chunkSize
await onProgress(min(processed, records.count), records.count)
if Task.isCancelled { throw CancellationError() }
}
}
}
actor DatabaseQueryExecutor {
let dbQueue: DatabaseQueue
func fetchUserWithPosts(userId: String) async throws -> (user: User, posts: [Post]) {
return try await dbQueue.read { db in
let user = try User.filter(Column("id") == userId).fetchOne(db)!
let posts = try Post
.filter(Column("userId") == userId)
.order(Column("createdAt").desc)
.limit(100)
.fetchAll(db)
return (user, posts)
}
}
}
Starting new feature?
└─ Is UI responsive with all operations on main thread?
├─ YES → Stay single-threaded (Step 1)
└─ NO → Continue...
└─ Do you have high-latency operations? (network, file I/O)
├─ YES → Add async/await (Step 2)
└─ NO → Continue...
└─ Do you have CPU-intensive work? (Instruments shows main thread busy)
├─ YES → Add @concurrent or nonisolated (Step 3)
└─ NO → Continue...
└─ Is main actor contention causing slowdowns?
└─ YES → Extract subsystem to actor (Step 4)
Error: "Main actor-isolated property accessed from nonisolated context"
├─ In delegate method?
│ └─ Pattern 2: Value Capture Before Task
├─ In async function?
│ └─ Add @MainActor or call from Task { @MainActor in }
└─ In @concurrent function?
└─ Move access to caller, use await, or make nonisolated
Error: "Type does not conform to Sendable"
├─ Enum/struct with Sendable properties?
│ └─ Add `: Sendable`
└─ Class?
└─ Make @MainActor or keep non-Sendable (don't share concurrently)
Want to offload work to background?
├─ Always background (image processing)?
│ └─ Use @concurrent (Swift 6.2+)
├─ Caller decides?
│ └─ Use nonisolated
└─ Too much main actor state?
└─ Extract to actor
Build Settings → Swift Compiler — Language
→ "Default Actor Isolation" = Main Actor
→ "Approachable Concurrency" = Yes
Build Settings → Swift Compiler — Concurrency
→ "Strict Concurrency Checking" = Complete
这启用了什么:
// ❌ 过早优化
@concurrent
func addNumbers(_ a: Int, _ b: Int) async -> Int {
return a + b // ❌ 简单工作,并发增加开销
}
// ✅ 保持简单
func addNumbers(_ a: Int, _ b: Int) -> Int {
return a + b
}
// ❌ 内存泄漏
progressTask = Task {
while true {
await self.update() // ❌ 强捕获
}
}
// ✅ 弱捕获
progressTask = Task { [weak self] in
guard let self = self else { return }
// ...
}
// ❌ 不要这样做
actor MyViewModel: ObservableObject { // ❌ UI 代码应该是 @MainActor!
@Published var state: State // ❌ 无法正常工作
}
// ✅ 这样做
@MainActor
class MyViewModel: ObservableObject {
@Published var state: State
}
@concurrent 用于始终在后台的工作(Swift 6.2+)nonisolated 用于库 API之前:随机崩溃、数据竞争、"在我机器上能运行"的 bug、过早的复杂性 之后:编译时保证、渐进式采用、仅在需要时使用并发
关键见解:Swift 6 的方法让你在编译成功之前证明代码是安全的。从简单开始,仅在性能分析证明需要时才增加复杂性。
WWDC:2025-268,2025-245,2022-110351,2021-10133
文档:/swift/adoptingswift6,/swift/sendable
技能:axiom-lldb(在调试器中调试 actor/任务状态)
最后更新:2025-12-01 状态:根据 WWDC 2025-268 渐进式旅程、@concurrent 属性、隔离一致性和可行并发模式进行了增强
每周安装次数
122
仓库
GitHub 星标数
601
首次出现
2026 年 1 月 21 日
安全审计
安装在
opencode105
codex100
gemini-cli98
claude-code96
cursor92
github-copilot90
Purpose : Progressive journey from single-threaded to concurrent Swift code Swift Version : Swift 6.0+, Swift 6.2+ for @concurrent iOS Version : iOS 17+ (iOS 18.2+ for @concurrent) Xcode : Xcode 16+ (Xcode 16.2+ for @concurrent) Context : WWDC 2025-268 "Embracing Swift concurrency" - approachable path to data-race safety
✅ Use this skill when :
@MainActor classes or async functions@MainActor, nonisolated, @concurrent, or actor isolationSendable❌ Do NOT use this skill for :
axiom-swiftui-debugging or axiom-swiftui-performance)Apple's Guidance (WWDC 2025-268) : "Your apps should start by running all of their code on the main thread, and you can get really far with single-threaded code."
Single-Threaded → Asynchronous → Concurrent → Actors
↓ ↓ ↓ ↓
Start here Hide latency Background Move data
(network) CPU work off main
When to advance :
Key insight : Concurrent code is more complex. Only introduce concurrency when profiling shows it's needed.
All code runs on the main thread by default in Swift 6.
// ✅ Simple, single-threaded
class ImageModel {
var imageCache: [URL: Image] = [:]
func fetchAndDisplayImage(url: URL) throws {
let data = try Data(contentsOf: url) // Reads local file
let image = decodeImage(data)
view.displayImage(image)
}
func decodeImage(_ data: Data) -> Image {
// Decode image data
return Image()
}
}
Main Actor Mode (Xcode 26+):
@MainActor unless explicitly marked otherwiseBuild Setting (Xcode 26+):
Build Settings → Swift Compiler — Language
→ "Default Actor Isolation" = Main Actor
Build Settings → Swift Compiler — Upcoming Features
→ "Approachable Concurrency" = Yes
When this is enough : If all operations are fast (<16ms for 60fps), stay single-threaded!
Add async/await when waiting on data (network, file I/O) would freeze UI.
// ❌ Blocks main thread until network completes
func fetchAndDisplayImage(url: URL) throws {
let (data, _) = try URLSession.shared.data(from: url) // ❌ Freezes UI!
let image = decodeImage(data)
view.displayImage(image)
}
// ✅ Suspends without blocking main thread
func fetchAndDisplayImage(url: URL) async throws {
let (data, _) = try await URLSession.shared.data(from: url) // ✅ Suspends here
let image = decodeImage(data) // ✅ Resumes here when data arrives
view.displayImage(image)
}
What happens :
await suspends function without blocking main threadCreate tasks in response to user events:
class ImageModel {
var url: URL = URL(string: "https://swift.org")!
func onTapEvent() {
Task { // ✅ Create task for user action
do {
try await fetchAndDisplayImage(url: url)
} catch {
displayError(error)
}
}
}
}
Multiple async tasks can run on the same thread by taking turns:
Task 1: [Fetch Image] → (suspend) → [Decode] → [Display]
Task 2: [Fetch News] → (suspend) → [Display News]
Main Thread Timeline:
[Fetch Image] → [Fetch News] → [Decode Image] → [Display Image] → [Display News]
Benefits :
When to use tasks :
Add concurrency when CPU-intensive work blocks UI.
Profiling shows decodeImage() takes 200ms, causing UI glitches:
func fetchAndDisplayImage(url: URL) async throws {
let (data, _) = try await URLSession.shared.data(from: url)
let image = decodeImage(data) // ❌ 200ms on main thread!
view.displayImage(image)
}
@concurrent Attribute (Swift 6.2+)Forces function to always run on background thread :
func fetchAndDisplayImage(url: URL) async throws {
let (data, _) = try await URLSession.shared.data(from: url)
let image = await decodeImage(data) // ✅ Runs on background thread
view.displayImage(image)
}
@concurrent
func decodeImage(_ data: Data) async -> Image {
// ✅ Always runs on background thread pool
// Good for: image processing, file I/O, parsing
return Image()
}
What@concurrent does:
@MainActor properties without awaitRequirements : Swift 6.2, Xcode 16.2+, iOS 18.2+
nonisolated (Library APIs)If providing a general-purpose API, use nonisolated instead:
// ✅ Stays on caller's actor
nonisolated
func decodeImage(_ data: Data) -> Image {
// Runs on whatever actor called it
// Main actor → stays on main actor
// Background → stays on background
return Image()
}
When to usenonisolated:
When to use@concurrent:
When you mark a function @concurrent, compiler shows main actor access:
@MainActor
class ImageModel {
var cachedImage: [URL: Image] = [:] // Main actor data
@concurrent
func decodeImage(_ data: Data, at url: URL) async -> Image {
if let image = cachedImage[url] { // ❌ Error: main actor access!
return image
}
// decode...
}
}
Strategy 1: Move to caller (keep work synchronous):
func fetchAndDisplayImage(url: URL) async throws {
// ✅ Check cache on main actor BEFORE async work
if let image = cachedImage[url] {
view.displayImage(image)
return
}
let (data, _) = try await URLSession.shared.data(from: url)
let image = await decodeImage(data) // No URL needed now
view.displayImage(image)
}
@concurrent
func decodeImage(_ data: Data) async -> Image {
// ✅ No main actor access needed
return Image()
}
Strategy 2: Use await (access main actor asynchronously):
@concurrent
func decodeImage(_ data: Data, at url: URL) async -> Image {
// ✅ Await to access main actor data
if let image = await cachedImage[url] {
return image
}
// decode...
}
Strategy 3: Make nonisolated (if doesn't need actor):
nonisolated
func decodeImage(_ data: Data) -> Image {
// ✅ No actor isolation, can call from anywhere
return Image()
}
When work runs on background:
Main Thread: [UI] → (suspend) → [UI Update]
↓
Background Pool: [Task A] → [Task B] → [Task A resumes]
Thread 1 Thread 2 Thread 3
Key points :
Add actors when too much code runs on main actor causing contention.
@MainActor
class ImageModel {
var cachedImage: [URL: Image] = [:]
let networkManager: NetworkManager = NetworkManager() // ❌ Also @MainActor
func fetchAndDisplayImage(url: URL) async throws {
// ✅ Background work...
let connection = await networkManager.openConnection(for: url) // ❌ Hops to main!
let data = try await connection.data(from: url)
await networkManager.closeConnection(connection, for: url) // ❌ Hops to main!
let image = await decodeImage(data)
view.displayImage(image)
}
}
Issue : Background task keeps hopping to main actor for network manager access.
// ✅ Move network state off main actor
actor NetworkManager {
var openConnections: [URL: Connection] = [:]
func openConnection(for url: URL) -> Connection {
if let connection = openConnections[url] {
return connection
}
let connection = Connection()
openConnections[url] = connection
return connection
}
func closeConnection(_ connection: Connection, for url: URL) {
openConnections.removeValue(forKey: url)
}
}
@MainActor
class ImageModel {
let networkManager: NetworkManager = NetworkManager()
func fetchAndDisplayImage(url: URL) async throws {
// ✅ Now runs mostly on background
let connection = await networkManager.openConnection(for: url)
let data = try await connection.data(from: url)
await networkManager.closeConnection(connection, for: url)
let image = await decodeImage(data)
view.displayImage(image)
}
}
What changed :
NetworkManager is now an actor instead of @MainActor class✅ Use actors for :
❌ Do NOT use actors for :
@MainActor@MainActor or non-SendableGuideline : Profile first. If main actor has too much state causing bottlenecks, extract one subsystem at a time into actors.
When data passes between actors or tasks, Swift checks it's Sendable (safe to share).
// ✅ Value types copy when passed
let url = URL(string: "https://swift.org")!
Task {
// ✅ This is a COPY of url, not the original
// URLSession.shared.data runs on background automatically
let data = try await URLSession.shared.data(from: url)
}
// ✅ Original url unchanged by background task
Why safe : Each actor gets its own independent copy. Changes don't affect other copies.
// ✅ Basic types
extension URL: Sendable {}
extension String: Sendable {}
extension Int: Sendable {}
extension Date: Sendable {}
// ✅ Collections of Sendable elements
extension Array: Sendable where Element: Sendable {}
extension Dictionary: Sendable where Key: Sendable, Value: Sendable {}
// ✅ Structs/enums with Sendable storage
struct Track: Sendable {
let id: String
let title: String
let duration: TimeInterval
}
enum PlaybackState: Sendable {
case stopped
case playing
case paused
}
// ✅ Main actor types
@MainActor class ImageModel {} // Implicitly Sendable (actor protects state)
// ✅ Actor types
actor NetworkManager {} // Implicitly Sendable (actor protects state)
// ❌ Classes are NOT Sendable by default
class MyImage {
var width: Int
var height: Int
var pixels: [Color]
func scale(by factor: Double) {
// Mutates shared state
}
}
let image = MyImage()
let otherImage = image // ✅ Both reference SAME object
image.scale(by: 0.5) // ✅ Changes visible through otherImage!
Problem with concurrency :
func scaleAndDisplay(imageName: String) {
let image = loadImage(imageName)
Task {
image.scale(by: 0.5) // Background task modifying
}
view.displayImage(image) // Main thread reading
// ❌ DATA RACE! Both threads could touch same object!
}
Solution 1: Finish modifications before sending :
@concurrent
func scaleAndDisplay(imageName: String) async {
let image = loadImage(imageName)
image.scale(by: 0.5) // ✅ All modifications on background
image.applyAnotherEffect() // ✅ Still on background
await view.displayImage(image) // ✅ Send to main actor AFTER modifications done
// ✅ Main actor now owns image exclusively
}
Solution 2: Don't share classes concurrently :
Keep model classes @MainActor or non-Sendable to prevent concurrent access.
Happens automatically when:
Passing data into/out of actors
Passing data into/out of tasks
Crossing actor boundaries with await
func fetchAndDisplayImage(url: URL) async throws { let (data, _) = try await URLSession.shared.data(from: url) // ↑ Sendable ↑ Sendable (crosses to background)
let image = await decodeImage(data)
// ↑ data crosses to background (must be Sendable)
// ↑ image returns to main (must be Sendable)
}
When : Type crosses actor boundaries
// ✅ Enum (no associated values)
private enum PlaybackState: Sendable {
case stopped
case playing
case paused
}
// ✅ Struct (all properties Sendable)
struct Track: Sendable {
let id: String
let title: String
let artist: String?
}
// ✅ Enum with Sendable associated values
enum Result: Sendable {
case success(data: Data)
case failure(error: Error) // Error is Sendable
}
When : nonisolated delegate method needs to update @MainActor state
nonisolated func delegate(_ param: SomeType) {
// ✅ Step 1: Capture delegate parameter values BEFORE Task
let value = param.value
let status = param.status
// ✅ Step 2: Task hop to MainActor
Task { @MainActor in
// ✅ Step 3: Safe to access self (we're on MainActor)
self.property = value
print("Status: \(status)")
}
}
Why : Delegate methods are nonisolated (called from library's threads). Capture parameters before Task. Accessing self inside Task { @MainActor in } is safe.
When : Task is stored as property OR runs for long time
class MusicPlayer {
private var progressTask: Task<Void, Never>?
func startMonitoring() {
progressTask = Task { [weak self] in // ✅ Weak capture
guard let self = self else { return }
while !Task.isCancelled {
await self.updateProgress()
}
}
}
deinit {
progressTask?.cancel()
}
}
Note : Short-lived Tasks (not stored) can use strong captures.
When : CPU-intensive work should always run on background (Swift 6.2+)
@concurrent
func decodeImage(_ data: Data) async -> Image {
// ✅ Always runs on background thread pool
// Good for: image processing, file I/O, JSON parsing
return Image()
}
// Usage
let image = await decodeImage(data) // Automatically offloads
Requirements : Swift 6.2, Xcode 16.2+, iOS 18.2+
When : Type needs to conform to protocol with specific actor isolation
protocol Exportable {
func export()
}
class PhotoProcessor {
@MainActor
func exportAsPNG() {
// Export logic requiring UI access
}
}
// ✅ Conform with explicit isolation
extension StickerModel: @MainActor Exportable {
func export() {
photoProcessor.exportAsPNG() // ✅ Safe: both on MainActor
}
}
When to use : Protocol methods need specific actor context (main actor for UI, background for processing)
When : Reading multiple properties that could change mid-access
var currentTime: TimeInterval {
get async {
// ✅ Cache reference for atomic snapshot
guard let player = player else { return 0 }
return player.currentTime
}
}
When : Code touches UI
@MainActor
class PlayerViewModel: ObservableObject {
@Published var currentTrack: Track?
@Published var isPlaying: Bool = false
func play(_ track: Track) async {
// Already on MainActor
self.currentTrack = track
self.isPlaying = true
}
}
actor DataFetcher {
let modelContainer: ModelContainer
func fetchAllTracks() async throws -> [Track] {
let context = ModelContext(modelContainer)
let descriptor = FetchDescriptor<Track>(
sortBy: [SortDescriptor(\.title)]
)
return try context.fetch(descriptor)
}
}
@MainActor
class TrackViewModel: ObservableObject {
@Published var tracks: [Track] = []
func loadTracks() async {
let fetchedTracks = try await fetcher.fetchAllTracks()
self.tracks = fetchedTracks // Back on MainActor
}
}
actor CoreDataFetcher {
func fetchTracksID(genre: String) async throws -> [String] {
let context = persistentContainer.newBackgroundContext()
var trackIDs: [String] = []
try await context.perform {
let request = NSFetchRequest<CDTrack>(entityName: "Track")
request.predicate = NSPredicate(format: "genre = %@", genre)
let results = try context.fetch(request)
trackIDs = results.map { $0.id } // Extract IDs before leaving context
}
return trackIDs // Lightweight, Sendable
}
}
actor DataImporter {
func importRecords(_ records: [RawRecord], onProgress: @MainActor (Int, Int) -> Void) async throws {
let chunkSize = 1000
let context = ModelContext(modelContainer)
for (index, chunk) in records.chunked(into: chunkSize).enumerated() {
for record in chunk {
context.insert(Track(from: record))
}
try context.save()
let processed = (index + 1) * chunkSize
await onProgress(min(processed, records.count), records.count)
if Task.isCancelled { throw CancellationError() }
}
}
}
actor DatabaseQueryExecutor {
let dbQueue: DatabaseQueue
func fetchUserWithPosts(userId: String) async throws -> (user: User, posts: [Post]) {
return try await dbQueue.read { db in
let user = try User.filter(Column("id") == userId).fetchOne(db)!
let posts = try Post
.filter(Column("userId") == userId)
.order(Column("createdAt").desc)
.limit(100)
.fetchAll(db)
return (user, posts)
}
}
}
Starting new feature?
└─ Is UI responsive with all operations on main thread?
├─ YES → Stay single-threaded (Step 1)
└─ NO → Continue...
└─ Do you have high-latency operations? (network, file I/O)
├─ YES → Add async/await (Step 2)
└─ NO → Continue...
└─ Do you have CPU-intensive work? (Instruments shows main thread busy)
├─ YES → Add @concurrent or nonisolated (Step 3)
└─ NO → Continue...
└─ Is main actor contention causing slowdowns?
└─ YES → Extract subsystem to actor (Step 4)
Error: "Main actor-isolated property accessed from nonisolated context"
├─ In delegate method?
│ └─ Pattern 2: Value Capture Before Task
├─ In async function?
│ └─ Add @MainActor or call from Task { @MainActor in }
└─ In @concurrent function?
└─ Move access to caller, use await, or make nonisolated
Error: "Type does not conform to Sendable"
├─ Enum/struct with Sendable properties?
│ └─ Add `: Sendable`
└─ Class?
└─ Make @MainActor or keep non-Sendable (don't share concurrently)
Want to offload work to background?
├─ Always background (image processing)?
│ └─ Use @concurrent (Swift 6.2+)
├─ Caller decides?
│ └─ Use nonisolated
└─ Too much main actor state?
└─ Extract to actor
Build Settings → Swift Compiler — Language
→ "Default Actor Isolation" = Main Actor
→ "Approachable Concurrency" = Yes
Build Settings → Swift Compiler — Concurrency
→ "Strict Concurrency Checking" = Complete
What this enables :
// ❌ Premature optimization
@concurrent
func addNumbers(_ a: Int, _ b: Int) async -> Int {
return a + b // ❌ Trivial work, concurrency adds overhead
}
// ✅ Keep simple
func addNumbers(_ a: Int, _ b: Int) -> Int {
return a + b
}
// ❌ Memory leak
progressTask = Task {
while true {
await self.update() // ❌ Strong capture
}
}
// ✅ Weak capture
progressTask = Task { [weak self] in
guard let self = self else { return }
// ...
}
// ❌ Don't do this
actor MyViewModel: ObservableObject { // ❌ UI code should be @MainActor!
@Published var state: State // ❌ Won't work correctly
}
// ✅ Do this
@MainActor
class MyViewModel: ObservableObject {
@Published var state: State
}
@concurrent for always-background work (Swift 6.2+)nonisolated for library APIsBefore : Random crashes, data races, "works on my machine" bugs, premature complexity After : Compile-time guarantees, progressive adoption, only use concurrency when needed
Key insight : Swift 6's approach makes you prove code is safe before compilation succeeds. Start simple, add complexity only when profiling proves it's needed.
WWDC : 2025-268, 2025-245, 2022-110351, 2021-10133
Docs : /swift/adoptingswift6, /swift/sendable
Skills : axiom-lldb (debug actor/task state in the debugger)
Last Updated : 2025-12-01 Status : Enhanced with WWDC 2025-268 progressive journey, @concurrent attribute, isolated conformances, and approachable concurrency patterns
Weekly Installs
122
Repository
GitHub Stars
601
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode105
codex100
gemini-cli98
claude-code96
cursor92
github-copilot90
Vue.js开发指南:最佳实践、组件设计与响应式编程核心原则
1,500 周安装