From ecc
Swift 6.2 Approachable Concurrency 패턴입니다. 기본은 단일 스레드 실행이며, 명시적 백그라운드 오프로딩에는 `@concurrent`, 메인 액터 타입에는 isolated conformance를 사용합니다.
npx claudepluginhub sam42-lab/everything-claude-code-krThis skill uses the workspace's default tool permissions.
코드는 기본적으로 단일 스레드에서 실행되고, 동시성은 명시적으로 도입되는 Swift 6.2의 동시성 모델을 적용하는 패턴입니다. 성능을 해치지 않으면서 흔한 데이터 레이스 오류를 줄입니다.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
코드는 기본적으로 단일 스레드에서 실행되고, 동시성은 명시적으로 도입되는 Swift 6.2의 동시성 모델을 적용하는 패턴입니다. 성능을 해치지 않으면서 흔한 데이터 레이스 오류를 줄입니다.
Swift 6.1 이하에서는 async 함수가 암묵적으로 백그라운드 스레드로 이동할 수 있었고, 겉으로 안전해 보이는 코드에서도 데이터 레이스 오류가 발생할 수 있었습니다.
// Swift 6.1: ERROR
@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 }
// Error: Sending 'self.photoProcessor' risks causing data races
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
}
}
Swift 6.2에서는 이 점이 바뀌었습니다. async 함수는 기본적으로 호출한 actor 위에 그대로 남습니다.
// Swift 6.2: OK — async stays on MainActor, no data race
@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 }
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
}
}
이제 MainActor 타입도 non-isolated 프로토콜에 안전하게 적합할 수 있습니다.
protocol Exportable {
func export()
}
// Swift 6.1: ERROR — crosses into main actor-isolated code
// Swift 6.2: OK with isolated conformance
extension StickerModel: @MainActor Exportable {
func export() {
photoProcessor.exportAsPNG()
}
}
컴파일러는 이 적합성이 메인 액터에서만 사용되도록 보장합니다.
// OK — ImageExporter is also @MainActor
@MainActor
struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // Safe: same actor isolation
}
}
// ERROR — nonisolated context can't use MainActor conformance
nonisolated struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // Error: Main actor-isolated conformance cannot be used here
}
}
전역/정적 상태는 MainActor로 보호합니다.
// Swift 6.1: ERROR — non-Sendable type may have shared mutable state
final class StickerLibrary {
static let shared: StickerLibrary = .init() // Error
}
// Fix: Annotate with @MainActor
@MainActor
final class StickerLibrary {
static let shared: StickerLibrary = .init() // OK
}
Swift 6.2에는 MainActor를 기본으로 추론하는 모드가 들어왔습니다. 수동 어노테이션이 크게 줄어듭니다.
// With MainActor default inference enabled:
final class StickerLibrary {
static let shared: StickerLibrary = .init() // Implicitly @MainActor
}
final class StickerModel {
let photoProcessor: PhotoProcessor
var selection: [PhotosPickerItem] // Implicitly @MainActor
}
extension StickerModel: Exportable { // Implicitly @MainActor conformance
func export() {
photoProcessor.exportAsPNG()
}
}
이 모드는 opt-in이며 앱, 스크립트, 기타 실행 타깃에 권장됩니다.
@concurrent실제 병렬 실행이 필요할 때만 @concurrent로 명시적으로 오프로딩합니다.
중요: 이 예시는 Approachable Concurrency 빌드 설정이 필요합니다. SE-0466(MainActor 기본 격리), SE-0461(NonisolatedNonsendingByDefault)을 켜야 합니다. 이 설정이 있으면
extractSticker는 호출한 actor에 남아 mutable 상태 접근이 안전해집니다. 이 설정이 없으면 데이터 레이스가 발생하며, 컴파일러가 이를 지적합니다.
nonisolated final class PhotoProcessor {
private 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
}
// Offload expensive work to concurrent thread pool
@concurrent
static func extractSubject(from data: Data) async -> Sticker { /* ... */ }
}
// Callers must await
let processor = PhotoProcessor()
processedPhotos[item.id] = await processor.extractSticker(data: data, with: item.id)
@concurrent를 쓰려면:
nonisolated@concurrent to the functionasync if not already asynchronousawait at call sites| Decision | Rationale |
|---|---|
| 기본 단일 스레드 | 가장 자연스러운 코드가 데이터 레이스 없이 동작하고, 동시성은 opt-in |
| async는 호출 actor에 남음 | 데이터 레이스를 유발하던 암묵적 오프로딩 제거 |
| isolated conformance | MainActor 타입이 위험한 우회 없이 프로토콜 적합 가능 |
@concurrent 명시적 opt-in | 백그라운드 실행은 우발적이 아니라 의도적인 성능 선택 |
| MainActor 기본 추론 | 앱 타깃에서 @MainActor 보일러플레이트 감소 |
| 점진적 도입 | 비파괴적 마이그레이션 경로 제공 |
SwiftSettings API in package manifest@concurrent where needed: Profile first, then offload hot paths@concurrent only for CPU-intensive work — image processing, compression, complex computationnonisolated workarounds or @Sendable wrappers@concurrent to every async function (most don't need background execution)nonisolated to suppress compiler errors without understanding isolationDispatchQueue patterns when actors provide the same safetymodel.availability checks in concurrency-related Foundation Models code