本文へ移動
cccskills
無料GitHub で公開

Swift Concurrency

Modern Swift concurrency with async/await, actors, TaskGroup, Sendable, and proper actor isolation for thread-safe iOS development

インストール方法を見る

含まれるファイル(2)

  • SKILL.md10.2 KB
  • references/migration-to-swift6.md3.4 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Swift Concurrency Skill

Master Swift 6 structured concurrency for safe, efficient concurrent code. Covers async/await, actors, tasks, and the Sendable protocol.

async/await Fundamentals

Basic Async Functions

// Async function declaration
func fetchUser(id: String) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    let (data, response) = try await URLSession.shared.data(from: url)

    guard let httpResponse = response as? HTTPURLResponse,
          httpResponse.statusCode == 200 else {
        throw NetworkError.invalidResponse
    }

    return try JSONDecoder().decode(User.self, from: data)
}

// Calling async functions
func loadProfile() async {
    do {
        let user = try await fetchUser(id: "123")
        print("Loaded: \(user.name)")
    } catch {
        print("Error: \(error)")
    }
}

Async Properties

struct RemoteImage {
    let url: URL

    var data: Data {
        get async throws {
            let (data, _) = try await URLSession.shared.data(from: url)
            return data
        }
    }
}

Async Sequences

// Creating async sequences
func fetchPages() -> AsyncThrowingStream<Page, Error> {
    AsyncThrowingStream { continuation in
        Task {
            var cursor: String? = nil
            repeat {
                let response = try await fetchPage(cursor: cursor)
                for page in response.pages {
                    continuation.yield(page)
                }
                cursor = response.nextCursor
            } while cursor != nil
            continuation.finish()
        }
    }
}

// Consuming async sequences
func processPages() async throws {
    for try await page in fetchPages() {
        process(page)
    }
}

Tasks

Unstructured Tasks

// Fire-and-forget task (inherits actor context)
func startBackgroundWork() {
    Task {
        await performWork()
    }
}

// Detached task (no inherited context)
func startDetachedWork() {
    Task.detached(priority: .background) {
        await performHeavyWork()
    }
}

Task Cancellation

func downloadWithCancellation() async throws -> Data {
    // Check for cancellation
    try Task.checkCancellation()

    // Or check manually
    if Task.isCancelled {
        throw CancellationError()
    }

    // Cooperative cancellation in loops
    for item in items {
        try Task.checkCancellation()
        await process(item)
    }

    return data
}

// Cancelling tasks
class DownloadManager {
    private var downloadTask: Task<Data, Error>?

    func startDownload() {
        downloadTask = Task {
            try await downloadLargeFile()
        }
    }

    func cancelDownload() {
        downloadTask?.cancel()
    }
}

TaskGroup for Parallel Work

// Parallel fetching with TaskGroup
func fetchAllUsers(ids: [String]) async throws -> [User] {
    try await withThrowingTaskGroup(of: User.self) { group in
        for id in ids {
            group.addTask {
                try await fetchUser(id: id)
            }
        }

        var users: [User] = []
        for try await user in group {
            users.append(user)
        }
        return users
    }
}

// With result collection
func fetchWithResults(ids: [String]) async -> [Result<User, Error>] {
    await withTaskGroup(of: Result<User, Error>.self) { group in
        for id in ids {
            group.addTask {
                do {
                    return .success(try await fetchUser(id: id))
                } catch {
                    return .failure(error)
                }
            }
        }

        var results: [Result<User, Error>] = []
        for await result in group {
            results.append(result)
        }
        return results
    }
}

Actors

Basic Actor

actor BankAccount {
    private var balance: Double = 0

    func deposit(_ amount: Double) {
        balance += amount
    }

    func withdraw(_ amount: Double) throws {
        guard balance >= amount else {
            throw BankError.insufficientFunds
        }
        balance -= amount
    }

    func getBalance() -> Double {
        balance
    }
}

// Usage (requires await)
let account = BankAccount()
await account.deposit(100)
let balance = await account.getBalance()

nonisolated Methods

actor DataStore {
    private var cache: [String: Data] = [:]

    // Requires isolation (default)
    func store(_ data: Data, for key: String) {
        cache[key] = data
    }

    // No isolation needed - can be called synchronously
    nonisolated var description: String {
        "DataStore instance"
    }

    // Computed from immutable state
    nonisolated let identifier = UUID()
}

Actor Reentrancy

actor ImageLoader {
    private var cache: [URL: Image] = [:]
    private var inProgress: [URL: Task<Image, Error>] = [:]

    func load(from url: URL) async throws -> Image {
        // Check cache first
        if let cached = cache[url] {
            return cached
        }

        // Check if already loading
        if let existing = inProgress[url] {
            return try await existing.value
        }

        // Start new load
        let task = Task {
            let image = try await downloadImage(from: url)
            cache[url] = image
            inProgress[url] = nil
            return image
        }

        inProgress[url] = task
        return try await task.value
    }
}

@MainActor

UI Updates on Main Thread

@MainActor
final class UserViewModel: ObservableObject {
    @Published var user: User?
    @Published var isLoading = false
    @Published var error: Error?

    private let repository: UserRepository

    init(repository: UserRepository) {
        self.repository = repository
    }

    func loadUser(id: String) async {
        isLoading = true
        defer { isLoading = false }

        do {
            user = try await repository.fetch(id: id)
        } catch {
            self.error = error
        }
    }
}

Calling MainActor from Background

actor DataProcessor {
    func process(items: [Item]) async {
        let results = heavyProcessing(items)

        // Switch to main actor for UI update
        await MainActor.run {
            UIManager.shared.updateUI(with: results)
        }
    }
}

Sendable Protocol

Understanding Sendable

// Value types are implicitly Sendable
struct Point: Sendable {
    var x: Double
    var y: Double
}

// Classes must be explicitly marked and thread-safe
final class Counter: Sendable {
    private let lock = NSLock()
    private var _count = 0

    var count: Int {
        lock.withLock { _count }
    }

    func increment() {
        lock.withLock { _count += 1 }
    }
}

// Actors are implicitly Sendable
actor SafeCounter: Sendable {
    var count = 0

    func increment() {
        count += 1
    }
}

@unchecked Sendable

// Use when you guarantee thread safety manually
final class LegacyCache: @unchecked Sendable {
    private let queue = DispatchQueue(label: "cache.queue")
    private var storage: [String: Any] = [:]

    func get(_ key: String) -> Any? {
        queue.sync { storage[key] }
    }

    func set(_ key: String, value: Any) {
        queue.async { [self] in
            storage[key] = value
        }
    }
}

Sendable Closures

actor DataManager {
    func process(completion: @Sendable @escaping () -> Void) {
        Task {
            await doWork()
            completion()
        }
    }
}

Global Actors

Custom Global Actors

@globalActor
actor DatabaseActor: GlobalActor {
    static let shared = DatabaseActor()
}

@DatabaseActor
final class DatabaseManager {
    private var connection: Connection?

    func query(_ sql: String) -> [Row] {
        // Runs on DatabaseActor
        connection?.execute(sql) ?? []
    }
}

// Usage
@DatabaseActor
func fetchRecords() async -> [Record] {
    let manager = DatabaseManager()
    return manager.query("SELECT * FROM records")
}

Common Patterns

Async Initialization

actor ConfiguredService {
    private var config: Config?

    func configure() async throws {
        config = try await loadConfig()
    }

    func performAction() async throws {
        guard let config else {
            throw ServiceError.notConfigured
        }
        // Use config
    }
}

// Factory pattern for async init
extension ConfiguredService {
    static func create() async throws -> ConfiguredService {
        let service = ConfiguredService()
        try await service.configure()
        return service
    }
}

Timeout Pattern

func fetchWithTimeout<T>(
    seconds: Double,
    operation: @escaping () async throws -> T
) async throws -> T {
    try await withThrowingTaskGroup(of: T.self) { group in
        group.addTask {
            try await operation()
        }

        group.addTask {
            try await Task.sleep(for: .seconds(seconds))
            throw TimeoutError()
        }

        let result = try await group.next()!
        group.cancelAll()
        return result
    }
}

// Usage
let user = try await fetchWithTimeout(seconds: 10) {
    try await fetchUser(id: "123")
}

Rate Limiting

actor RateLimiter {
    private let limit: Int
    private let interval: Duration
    private var requests: [Date] = []

    init(limit: Int, per interval: Duration) {
        self.limit = limit
        self.interval = interval
    }

    func acquire() async throws {
        let now = Date()
        let cutoff = now.addingTimeInterval(-interval.timeInterval)
        requests = requests.filter { $0 > cutoff }

        if requests.count >= limit {
            let waitTime = requests.first!.timeIntervalSince(cutoff)
            try await Task.sleep(for: .seconds(waitTime))
            try await acquire()
        } else {
            requests.append(now)
        }
    }
}

References

  • See references/migration-to-swift6.md for Swift 6 strict concurrency migration
  • See references/concurrency-debugging.md for debugging race conditions

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

When the user wants to A/B test App Store product page elements to improve conversion rate. Also use when the user mentions "A/B test", "product page optimization", "test my screenshots", "test my icon", "conversion rate optimization", "CPP", or "custom product pages". For screenshot design, see screenshot-optimization. For metadata optimization, see metadata-optimization.

日本語の概要は準備中です。原文の説明を表示しています。

JordanCoin/ios-skills-collection62026年9月10日 更新

Discover and configure Bluetooth and Wi-Fi accessories using AccessorySetupKit. Use when presenting a privacy-preserving accessory picker, defining discovery descriptors for BLE or Wi-Fi devices, handling accessory session events, migrating from CoreBluetooth permission-based scanning, or setting up accessories without requiring broad Bluetooth permissions.

日本語の概要は準備中です。原文の説明を表示しています。

JordanCoin/ios-skills-collection62026年9月10日 更新

Implement, review, or improve Live Activities and Dynamic Island experiences in iOS apps using ActivityKit. Use when building real-time updating widgets for the Lock Screen and Dynamic Island — delivery tracking, sports scores, ride-sharing status, workout timers, media playback, or any time-sensitive information that updates in real time. Also use when working with ActivityKit, ActivityAttributes, Activity lifecycle (request/update/end), Dynamic Island layouts (compact/minimal/expanded), push-to-update Live Activities, or Lock Screen live widgets.

日本語の概要は準備中です。原文の説明を表示しています。

JordanCoin/ios-skills-collection62026年9月10日 更新

Measure ad effectiveness with privacy-preserving attribution using AdAttributionKit. Use when registering ad impressions, handling attribution postbacks, updating conversion values, implementing re-engagement attribution, configuring publisher or advertiser apps, or replacing SKAdNetwork with AdAttributionKit for ad measurement.

日本語の概要は準備中です。原文の説明を表示しています。

JordanCoin/ios-skills-collection62026年9月10日 更新

alarmkit

無料

Implement AlarmKit alarms and countdown timers for iOS and iPadOS with Lock Screen, Dynamic Island, and Apple Watch system UI. Covers AlarmManager scheduling, AlarmAttributes and AlarmPresentation, AlarmButton stop and snooze actions, authorization, state observation, and Live Activity integration. Use when building wake-up alarms, countdown timers, or alarm-style alerts that need Apple's system alarm experience.

日本語の概要は準備中です。原文の説明を表示しています。

JordanCoin/ios-skills-collection62026年9月10日 更新

When the user wants to optimize their Google Play Store listing — title, short description, full description, keywords, ratings, or Play Store-specific features. Use when the user mentions "Google Play", "Android", "Play Store", "Play Console", "short description", "full description indexed", "Google Play ASO", or wants Google Play-specific keyword, creative, or ratings strategy. For iOS App Store optimization, see aso-audit and metadata-optimization.

日本語の概要は準備中です。原文の説明を表示しています。

JordanCoin/ios-skills-collection62026年9月10日 更新

JordanCoin のスキルをすべて見る

このスキルの問題を報告する