swift-concurrency by dpearson2699/swift-ios-skills
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swift-concurrency审查、修复并编写面向 Swift 6.2+ 的并发 Swift 代码。应用参与者隔离、Sendable 安全性以及现代并发模式,同时将行为变更降至最低。
诊断并发问题时,请遵循以下步骤:
@MainActor、自定义 actor、nonisolated)以及是否有默认隔离模式处于活动状态。优先选择在满足数据竞争安全性的同时保留现有行为的修改。
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 情况 | 推荐修复 |
|---|
| UI 绑定类型 | 使用 @MainActor 注解类型或相关成员。 |
| MainActor 类型的协议一致性 | 使用隔离一致性:extension Foo: @MainActor Proto。 |
| 全局 / 静态状态 | 使用 @MainActor 保护或移入参与者。 |
| 需要后台工作 | 在 nonisolated 类型上使用 @concurrent 异步函数。 |
| Sendable 错误 | 优先使用不可变值类型。仅在正确时添加 Sendable。 |
| 跨隔离回调 | 使用 sending 参数(SE-0430)进行更精细的控制。 |
@unchecked Sendable 或 nonisolated(unsafe)。Swift 6.2 引入了“易用并发”——一系列语言变更,默认情况下使并发代码更安全,同时减少了注解负担。
使用 -default-isolation MainActor 编译器标志(或 Xcode 26 的“Approachable Concurrency”构建设置),模块中的所有代码默认在 @MainActor 上运行,除非显式选择退出。
效果: 无需到处编写 @MainActor,即可消除 UI 绑定代码和全局/静态状态的大多数数据竞争安全错误。
// 启用默认 MainActor 隔离后,这些代码隐式具有 @MainActor 隔离:
final class StickerLibrary {
static let shared = StickerLibrary() // 安全 —— 在 MainActor 上
var stickers: [Sticker] = []
}
final class StickerModel {
let photoProcessor = PhotoProcessor()
var selection: [PhotosPickerItem] = []
}
// 协议一致性也隐式隔离:
extension StickerModel: Exportable {
func export() {
photoProcessor.exportAsPNG()
}
}
何时使用: 推荐用于大多数代码与 UI 绑定的应用程序、脚本和其他可执行目标。不推荐用于应保持参与者无关的库目标。
非隔离异步函数现在默认停留在调用者的参与者上,而不是跳转到全局并发执行器。这就是 nonisolated(nonsending) 行为。
class PhotoProcessor {
func extractSticker(data: Data, with id: String?) async -> Sticker? {
// 在 Swift 6.2 中,此函数在调用者的参与者(例如 MainActor)上运行,
// 而不是跳转到后台线程。
// ...
}
}
@MainActor
final class StickerModel {
let photoProcessor = PhotoProcessor()
func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? {
guard let data = try await item.loadTransferable(type: Data.self) else {
return nil
}
// 无数据竞争 —— photoProcessor 停留在 MainActor 上
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
}
}
需要时使用 @concurrent 显式请求后台执行。
@concurrent 确保函数始终在并发线程池上运行,从而释放调用参与者以运行其他任务。
class PhotoProcessor {
var cachedStickers: [String: Sticker] = [:]
func extractSticker(data: Data, with id: String) async -> Sticker {
if let sticker = cachedStickers[id] { return sticker }
let sticker = await Self.extractSubject(from: data)
cachedStickers[id] = sticker
return sticker
}
@concurrent
static func extractSubject(from data: Data) async -> Sticker {
// 昂贵的图像处理 —— 在后台线程池上运行
// ...
}
}
要将函数移至后台线程:
nonisolated(或函数本身是)。@concurrent。async。await。nonisolated struct PhotoProcessor {
@concurrent
func process(data: Data) async -> ProcessedPhoto? { /* ... */ }
}
// 调用者:
processedPhotos[item.id] = await PhotoProcessor().process(data: data)
Task.immediate 在任何挂起点之前,在当前参与者上同步开始执行,而不是被排队。
Task.immediate { await handleUserInput() }
用于应无延迟开始的延迟敏感工作。还有 Task.immediateDetached,它结合了立即启动和分离语义。
Observations { } 通过 AsyncSequence 提供对 @Observable 类型的异步观察,支持事务性变更跟踪。
for await _ in Observations { model.count } {
print("Count changed to \(model.count)")
}
不可变弱引用(weak let)使持有弱引用的类型能够符合 Sendable。在 SE-0481 中提出;可能尚未在发布的工具链中可用。
需要 MainActor 状态的一致性称为 隔离一致性。编译器确保它仅在匹配的隔离上下文中使用。
protocol Exportable {
func export()
}
// 隔离一致性:仅在 MainActor 上可用
extension StickerModel: @MainActor Exportable {
func export() {
photoProcessor.exportAsPNG()
}
}
@MainActor
struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // 正确 —— ImageExporter 在 MainActor 上
}
}
如果 ImageExporter 是 nonisolated,添加 StickerModel 将会失败:“'StickerModel' 到 'Exportable' 的主参与者隔离一致性不能在非隔离上下文中使用。”
@MainActor。没有例外。let)属性或纯计算的方法使用 nonisolated。@concurrent 显式将工作移出调用者的参与者。nonisolated(unsafe)。NSLock、DispatchSemaphore)。Sendable 时,值类型(结构体、枚举)自动成为 Sendable。Sendable。@MainActor 类隐式是 Sendable。不要添加冗余的 Sendable 一致性。final,且所有存储属性为 let 和 Sendable。@unchecked Sendable 是最后的手段。记录编译器无法证明安全性的原因。sending 参数(SE-0430)进行更细粒度的隔离控制。@preconcurrency import。计划移除它。Task: 非结构化,继承调用者上下文。
Task { await doWork() }
Task.detached: 无继承上下文。仅在明确需要打破隔离继承时使用。
Task.immediate: 在当前参与者上立即启动。用于延迟敏感的工作。
Task.immediate { await handleUserInput() }
async let: 固定数量的并发操作。
async let a = fetchA()
async let b = fetchB()
let result = try await (a, b)
TaskGroup: 动态数量的并发操作。
try await withThrowingTaskGroup(of: Item.self) { group in
for id in ids {
group.addTask { try await fetch(id) }
}
for try await item in group { process(item) }
}
Task.isCancelled 或调用 try Task.checkCancellation()。.task 修饰符——它会在视图消失时处理取消。withTaskCancellationHandler 进行清理。deinit 或 onDisappear 中取消存储的任务。参与者是可重入的。状态可能在挂起点之间发生变化。
// 错误:状态可能在 await 期间改变
actor Counter {
var count = 0
func increment() async {
let current = count
await someWork()
count = current + 1 // BUG: count 可能已改变
}
}
// 正确:同步修改,无重入风险
actor Counter {
var count = 0
func increment() { count += 1 }
}
使用 AsyncStream 桥接回调/委托 API:
let stream = AsyncStream<Location> { continuation in
let delegate = LocationDelegate { location in
continuation.yield(location)
}
continuation.onTermination = { _ in delegate.stop() }
delegate.start()
}
对单值回调使用 withCheckedContinuation / withCheckedThrowingContinuation。确保恰好恢复一次。
@Observable 类应该是 @MainActor。@State 来拥有 @Observable 实例(替代 @StateObject)。Observations { }(SE-0475)将 @Observable 属性作为 AsyncSequence 进行异步观察。当参与者不适用时——例如需要同步访问、性能关键路径或桥接 C/ObjC——使用低级同步原语:
Mutex<Value>(iOS 18+,Synchronization 模块):新代码的首选锁。将受保护状态存储在锁内部。使用 withLock { } 模式。OSAllocatedUnfairLock(iOS 16+,os 模块):针对旧版 iOS 版本时使用。支持用于调试的所有权断言。Atomic<Value>(iOS 18+,Synchronization 模块):用于简单计数器和标志的无锁原子操作。需要显式内存排序。关键规则: 绝不在参与者内部使用锁(双重同步),也绝不跨 await 持有锁(死锁风险)。有关完整的 API 详情、代码示例以及选择锁与参与者的决策指南,请参阅 references/synchronization-primitives.md。
@MainActor 上进行繁重计算会冻结 UI。移至 @concurrent 函数。@MainActor。只有涉及 UI 的代码需要。Sendable 结构体,而不是参与者。Task 引用并取消它们,或使用 .task 视图修饰符。self 时,使用 [weak self]。DispatchSemaphore.wait() 会导致死锁。改用结构化并发。@MainActor 和 nonisolated 属性。应一致地隔离整个类型。@MainActor func 而不是 await MainActor.run { }。@MainActor 上没有阻塞调用Sendable 一致性正确(无不当的 @unchecked)@preconcurrency 导入已记录移除计划@concurrent,而非 @MainActor.task 修饰符,而非手动 Task 管理references/swift-6-2-concurrency.md。references/approachable-concurrency.md。references/swiftui-concurrency.md。references/synchronization-primitives.md。每周安装数
390
代码仓库
GitHub 星标数
269
首次出现
2026年3月3日
安全审计
安装于
codex386
opencode385
cursor384
gemini-cli384
amp384
cline384
Review, fix, and write concurrent Swift code targeting Swift 6.2+. Apply actor isolation, Sendable safety, and modern concurrency patterns with minimal behavior changes.
When diagnosing a concurrency issue, follow this sequence:
@MainActor, custom actor, nonisolated) and whether a default isolation mode is active.Prefer edits that preserve existing behavior while satisfying data-race safety.
| Situation | Recommended fix |
|---|---|
| UI-bound type | Annotate the type or relevant members with @MainActor. |
| Protocol conformance on MainActor type | Use an isolated conformance: extension Foo: @MainActor Proto. |
| Global / static state | Protect with @MainActor or move into an actor. |
| Background work needed | Use a @concurrent async function on a nonisolated type. |
| Sendable error | Prefer immutable value types. Add Sendable only when correct. |
@unchecked Sendable or nonisolated(unsafe) was added.Swift 6.2 introduces "approachable concurrency" -- a set of language changes that make concurrent code safer by default while reducing annotation burden.
With the -default-isolation MainActor compiler flag (or the Xcode 26 "Approachable Concurrency" build setting), all code in a module runs on @MainActor by default unless explicitly opted out.
Effect: Eliminates most data-race safety errors for UI-bound code and global/static state without writing @MainActor everywhere.
// With default MainActor isolation enabled, these are implicitly @MainActor:
final class StickerLibrary {
static let shared = StickerLibrary() // safe -- on MainActor
var stickers: [Sticker] = []
}
final class StickerModel {
let photoProcessor = PhotoProcessor()
var selection: [PhotosPickerItem] = []
}
// Conformances are also implicitly isolated:
extension StickerModel: Exportable {
func export() {
photoProcessor.exportAsPNG()
}
}
When to use: Recommended for apps, scripts, and other executable targets where most code is UI-bound. Not recommended for library targets that should remain actor-agnostic.
Nonisolated async functions now stay on the caller's actor by default instead of hopping to the global concurrent executor. This is the nonisolated(nonsending) behavior.
class PhotoProcessor {
func extractSticker(data: Data, with id: String?) async -> Sticker? {
// In Swift 6.2, this runs on the caller's actor (e.g., MainActor)
// instead of hopping to a background thread.
// ...
}
}
@MainActor
final class StickerModel {
let photoProcessor = PhotoProcessor()
func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? {
guard let data = try await item.loadTransferable(type: Data.self) else {
return nil
}
// No data race -- photoProcessor stays on MainActor
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
}
}
Use @concurrent to explicitly request background execution when needed.
@concurrent ensures a function always runs on the concurrent thread pool, freeing the calling actor to run other tasks.
class PhotoProcessor {
var cachedStickers: [String: Sticker] = [:]
func extractSticker(data: Data, with id: String) async -> Sticker {
if let sticker = cachedStickers[id] { return sticker }
let sticker = await Self.extractSubject(from: data)
cachedStickers[id] = sticker
return sticker
}
@concurrent
static func extractSubject(from data: Data) async -> Sticker {
// Expensive image processing -- runs on background thread pool
// ...
}
}
To move a function to a background thread:
nonisolated (or the function itself is).@concurrent to the function.async if not already asynchronous.await at call sites.nonisolated struct PhotoProcessor {
@concurrent
func process(data: Data) async -> ProcessedPhoto? { /* ... */ }
}
// Caller:
processedPhotos[item.id] = await PhotoProcessor().process(data: data)
Task.immediate starts executing synchronously on the current actor before any suspension point, rather than being enqueued.
Task.immediate { await handleUserInput() }
Use for latency-sensitive work that should begin without delay. There is also Task.immediateDetached which combines immediate start with detached semantics.
Observations { } provides async observation of @Observable types via AsyncSequence, enabling transactional change tracking.
for await _ in Observations { model.count } {
print("Count changed to \(model.count)")
}
Immutable weak references (weak let) that enable Sendable conformance for types holding weak references. Proposed in SE-0481; may not yet be available in shipping toolchains.
A conformance that needs MainActor state is called an isolated conformance. The compiler ensures it is only used in a matching isolation context.
protocol Exportable {
func export()
}
// Isolated conformance: only usable on MainActor
extension StickerModel: @MainActor Exportable {
func export() {
photoProcessor.exportAsPNG()
}
}
@MainActor
struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // OK -- ImageExporter is on MainActor
}
}
If ImageExporter were nonisolated, adding a StickerModel would fail: "Main actor-isolated conformance of 'StickerModel' to 'Exportable' cannot be used in nonisolated context."
@MainActor for all UI-touching code. No exceptions.nonisolated only for methods that access immutable (let) properties or are pure computations.@concurrent to explicitly move work off the caller's actor.nonisolated(unsafe) unless you have proven internal synchronization and exhausted all other options.NSLock, DispatchSemaphore) inside actors.Sendable when all stored properties are Sendable.Sendable.@MainActor classes are implicitly Sendable. Do NOT add redundant Sendable conformance.final with all stored properties let and Sendable.@unchecked Sendable is a last resort. Document why the compiler cannot prove safety.Task: Unstructured, inherits caller context.
Task { await doWork() }
Task.detached: No inherited context. Use only when you explicitly need to break isolation inheritance.
Task.immediate: Starts immediately on current actor. Use for latency-sensitive work.
Task.immediate { await handleUserInput() }
async let: Fixed number of concurrent operations.
async let a = fetchA()
async let b = fetchB()
let result = try await (a, b)
TaskGroup: Dynamic number of concurrent operations.
try await withThrowingTaskGroup(of: Item.self) { group in
for id in ids {
group.addTask { try await fetch(id) }
}
for try await item in group { process(item) }
}
Task.isCancelled or call try Task.checkCancellation() in loops..task modifier in SwiftUI -- it handles cancellation on view disappear.withTaskCancellationHandler for cleanup.deinit or onDisappear.Actors are reentrant. State can change across suspension points.
// WRONG: State may change during await
actor Counter {
var count = 0
func increment() async {
let current = count
await someWork()
count = current + 1 // BUG: count may have changed
}
}
// CORRECT: Mutate synchronously, no reentrancy risk
actor Counter {
var count = 0
func increment() { count += 1 }
}
Use AsyncStream to bridge callback/delegate APIs:
let stream = AsyncStream<Location> { continuation in
let delegate = LocationDelegate { location in
continuation.yield(location)
}
continuation.onTermination = { _ in delegate.stop() }
delegate.start()
}
Use withCheckedContinuation / withCheckedThrowingContinuation for single-value callbacks. Resume exactly once.
@Observable classes should be @MainActor for view models.@State to own an @Observable instance (replaces @StateObject).Observations { } (SE-0475) for async observation of @Observable properties as an AsyncSequence.When actors are not the right fit — synchronous access, performance-critical paths, or bridging C/ObjC — use low-level synchronization primitives:
Mutex<Value> (iOS 18+, Synchronization module): Preferred lock for new code. Stores protected state inside the lock. withLock { } pattern.OSAllocatedUnfairLock (iOS 16+, os module): Use when targeting older iOS versions. Supports ownership assertions for debugging.Atomic<Value> (iOS 18+, Synchronization module): Lock-free atomics for simple counters and flags. Requires explicit memory ordering.Key rule: Never put locks inside actors (double synchronization), and never hold a lock across await (deadlock risk). See references/synchronization-primitives.md for full API details, code examples, and a decision guide for choosing locks vs actors.
@MainActor freezes UI. Move to a @concurrent function.@MainActor. Only UI-touching code does.Sendable struct, not an actor.Task references and cancel them, or use the .task view modifier.[weak self] when capturing in long-lived stored tasks.@MainActorSendable conformance is correct (no unjustified @unchecked)@preconcurrency imports are documented with removal plan@concurrent, not @MainActor.task modifier used in SwiftUI instead of manual Task managementreferences/swift-6-2-concurrency.md for detailed Swift 6.2 changes, patterns, and migration examples.references/approachable-concurrency.md for the approachable concurrency mode quick-reference guide.references/swiftui-concurrency.md for SwiftUI-specific concurrency guidance.references/synchronization-primitives.md for Mutex, OSAllocatedUnfairLock, and guidance on choosing locks vs actors.Weekly Installs
390
Repository
GitHub Stars
269
First Seen
Mar 3, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex386
opencode385
cursor384
gemini-cli384
amp384
cline384
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
106,200 周安装
| Cross-isolation callback | Use sending parameters (SE-0430) for finer control. |
sending parameters (SE-0430) for finer-grained isolation control.@preconcurrency import only for third-party libraries you cannot modify. Plan to remove it.selfDispatchSemaphore.wait() in async code will deadlock. Use structured concurrency instead.@MainActor and nonisolated properties in one type. Isolate the entire type consistently.@MainActor func over await MainActor.run { }.