All skills

Comprehensive macOS development guidance including Swift 6+, SwiftUI, SwiftData, architecture patterns, AppKit bridging, and macOS 26 Tahoe APIs. Use for macOS code review, best practices, UI review, or platform-specific features.

  • 40 files
  • 300.9 KB
  • Updated 3 months ago
  • GitHub

Use this Skill: https://skilld.dev/gh/rshankras/claude-code-apple-skills/macos

This session only. Nothing lands on disk.

coding-best-practicesmodern-concurrency.md

≈3.7k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Modern Concurrency Best Practices

Async/await, actors, structured concurrency, and Swift 6 concurrency patterns for macOS.

Async/Await Basics

Converting from Completion Handlers

// ❌ OLD: Completion handler
func fetchUser(id: UUID, completion: @escaping (Result<User, Error>) -> Void) {
    URLSession.shared.dataTask(with: url) { data, response, error in
        // Handle callback
    }.resume()
}

// Usage with pyramid of doom
fetchUser(id: userID) { result in
    switch result {
    case .success(let user):
        fetchPosts(for: user) { result in
            switch result {
            case .success(let posts):
                // More nesting...
            case .failure(let error):
                // Handle error
            }
        }
    case .failure(let error):
        // Handle error
    }
}

// ✅ GOOD: Async/await
func fetchUser(id: UUID) async throws -> User {
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}

func fetchPosts(for user: User) async throws -> [Post] {
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode([Post].self, from: data)
}

// Usage - clean and linear
do {
    let user = try await fetchUser(id: userID)
    let posts = try await fetchPosts(for: user)
    // Process posts
} catch {
    // Handle error
}

Async Properties

// ✅ GOOD: Async computed property
class ImageLoader {
    var image: UIImage {
        get async throws {
            let (data, _) = try await URLSession.shared.data(from: imageURL)
            guard let image = UIImage(data: data) else {
                throw ImageError.invalidData
            }
            return image
        }
    }
}

// Usage
let image = try await imageLoader.image

Task Management

Creating and Managing Tasks

// ✅ GOOD: Unstructured task
class ViewController: NSViewController {
    private var loadingTask: Task<Void, Never>?

    func loadData() {
        loadingTask = Task {
            do {
                let data = try await fetchData()
                await updateUI(with: data)
            } catch {
                await showError(error)
            }
        }
    }

    override func viewDidDisappear() {
        super.viewDidDisappear()
        loadingTask?.cancel()  // Cancel when view disappears
    }
}

// ✅ GOOD: Detached task (runs independently)
Task.detached {
    // Runs with default priority, no parent context
    let result = await heavyComputation()
    await MainActor.run {
        // Update UI on main thread
        updateUI(with: result)
    }
}

Task Groups for Parallel Execution

// ✅ GOOD: Parallel fetching with task group
func fetchAllArticles(ids: [UUID]) async throws -> [Article] {
    try await withThrowingTaskGroup(of: Article.self) { group in
        for id in ids {
            group.addTask {
                try await self.fetchArticle(id: id)
            }
        }

        var articles: [Article] = []
        for try await article in group {
            articles.append(article)
        }
        return articles
    }
}

// ✅ GOOD: With error handling per task
func fetchAllArticles(ids: [UUID]) async -> [Result<Article, Error>] {
    await withTaskGroup(of: Result<Article, Error>.self) { group in
        for id in ids {
            group.addTask {
                do {
                    let article = try await self.fetchArticle(id: id)
                    return .success(article)
                } catch {
                    return .failure(error)
                }
            }
        }

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

Async Sequences

// ✅ GOOD: Processing async sequences
func processLines(from url: URL) async throws {
    let lines = url.lines  // AsyncSequence

    for try await line in lines {
        processLine(line)
    }
}

// ✅ GOOD: Custom async sequence
struct NumberGenerator: AsyncSequence {
    typealias Element = Int

    let range: Range<Int>

    struct AsyncIterator: AsyncIteratorProtocol {
        var current: Int
        let end: Int

        mutating func next() async -> Int? {
            guard current < end else { return nil }
            let value = current
            current += 1
            try? await Task.sleep(for: .milliseconds(100))  // Simulate delay
            return value
        }
    }

    func makeAsyncIterator() -> AsyncIterator {
        AsyncIterator(current: range.lowerBound, end: range.upperBound)
    }
}

// Usage
for await number in NumberGenerator(range: 0..<10) {
    print(number)
}

Actors for Thread Safety

Basic Actor Usage

// ❌ BAD: Unsafe class with mutable state
class Counter {
    var value = 0  // Race condition!

    func increment() {
        value += 1
    }
}

// ✅ GOOD: Thread-safe actor
actor Counter {
    private var value = 0

    func increment() {
        value += 1
    }

    func getValue() -> Int {
        value
    }
}

// Usage (async required)
let counter = Counter()
await counter.increment()
let value = await counter.getValue()

Actor Isolation

actor DataManager {
    private var cache: [UUID: Data] = [:]

    // Isolated to actor - synchronous within actor
    func updateCache(id: UUID, data: Data) {
        cache[id] = data
    }

    // Non-isolated - can be called synchronously
    nonisolated func generateID() -> UUID {
        UUID()  // Pure function, no actor state access
    }

    // Isolated - async from outside
    func getData(id: UUID) -> Data? {
        cache[id]
    }
}

// Usage
let manager = DataManager()
let id = manager.generateID()  // Synchronous - nonisolated
await manager.updateCache(id: id, data: data)  // Async - isolated

Global Actors

// ✅ GOOD: MainActor for UI updates
@MainActor
class ViewModel: ObservableObject {
    @Published var items: [Item] = []

    // Runs on main thread
    func updateItems(_ newItems: [Item]) {
        items = newItems
    }

    // Explicitly run off main thread
    nonisolated func processInBackground() async {
        // Heavy computation off main thread
        let processed = await heavyComputation()

        // Switch back to main thread for UI update
        await updateItems(processed)
    }
}

// Individual functions can be marked
@MainActor
func updateUI() {
    // Guaranteed to run on main thread
}

// Mix isolated and non-isolated in same type
class MixedClass {
    @MainActor
    var uiProperty: String = ""

    nonisolated
    func backgroundWork() {
        // Runs on background thread
    }
}

Sendable Protocol

Sendable Types

// ✅ GOOD: Value types are automatically Sendable
struct User: Sendable {
    let id: UUID
    let name: String
}

// ✅ GOOD: Immutable classes can be Sendable
final class Configuration: Sendable {
    let apiKey: String
    let baseURL: URL

    init(apiKey: String, baseURL: URL) {
        self.apiKey = apiKey
        self.baseURL = baseURL
    }
}

// ❌ BAD: Mutable class can't be Sendable safely
final class Counter: Sendable {  // Warning!
    var count = 0  // Mutable property not thread-safe
}

// ✅ GOOD: Use actor instead
actor Counter {
    var count = 0  // Thread-safe via actor isolation
}

// ✅ GOOD: @unchecked Sendable when you know it's safe
final class ThreadSafeCounter: @unchecked Sendable {
    private let lock = NSLock()
    private var _count = 0

    var count: Int {
        lock.lock()
        defer { lock.unlock() }
        return _count
    }

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

Sendable Closures

// ✅ GOOD: Sendable closure
func processAsync(_ handler: @Sendable () -> Void) async {
    await Task.detached {
        handler()
    }.value
}

// ❌ BAD: Capturing mutable state
var counter = 0
processAsync {
    counter += 1  // Error: capturing mutable state
}

// ✅ GOOD: Use actor for mutable state
actor SharedCounter {
    var value = 0
}

let sharedCounter = SharedCounter()
processAsync {
    await sharedCounter.increment()
}

Concurrency Patterns

Repository Pattern with Async/Await

protocol ArticleRepository {
    func fetchArticles() async throws -> [Article]
    func save(_ article: Article) async throws
}

actor SwiftDataArticleRepository: ArticleRepository {
    private let modelContext: ModelContext

    init(modelContext: ModelContext) {
        self.modelContext = modelContext
    }

    func fetchArticles() async throws -> [Article] {
        let descriptor = FetchDescriptor<Article>()
        return try modelContext.fetch(descriptor)
    }

    func save(_ article: Article) async throws {
        modelContext.insert(article)
        try modelContext.save()
    }
}

ViewModel with Async Operations

@MainActor
class ArticleListViewModel: ObservableObject {
    @Published var articles: [Article] = []
    @Published var isLoading = false
    @Published var error: Error?

    private let repository: ArticleRepository

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

    func loadArticles() async {
        isLoading = true
        error = nil

        do {
            articles = try await repository.fetchArticles()
        } catch {
            self.error = error
        }

        isLoading = false
    }

    func refresh() async {
        await loadArticles()
    }
}

// Usage in SwiftUI
struct ArticleListView: View {
    @StateObject private var viewModel: ArticleListViewModel

    var body: some View {
        List(viewModel.articles) { article in
            Text(article.title)
        }
        .task {
            await viewModel.loadArticles()
        }
        .refreshable {
            await viewModel.refresh()
        }
    }
}

Cancellation Handling

// ✅ GOOD: Checking for cancellation
func performLongOperation() async throws {
    for i in 0..<1000 {
        // Check if task was cancelled
        try Task.checkCancellation()

        // Or check manually
        if Task.isCancelled {
            cleanup()
            return
        }

        await processItem(i)
    }
}

// ✅ GOOD: Cancellation in view model
@MainActor
class SearchViewModel: ObservableObject {
    @Published var results: [Result] = []
    private var searchTask: Task<Void, Never>?

    func search(_ query: String) {
        // Cancel previous search
        searchTask?.cancel()

        searchTask = Task {
            do {
                try await Task.sleep(for: .milliseconds(300))  // Debounce
                try Task.checkCancellation()

                let results = try await performSearch(query)
                self.results = results
            } catch is CancellationError {
                // Ignore cancellation
            } catch {
                // Handle other errors
            }
        }
    }
}

Concurrency Best Practices

Avoid Blocking Main Thread

// ❌ BAD: Blocking main thread
@MainActor
func loadData() {
    let data = heavyComputation()  // Blocks UI!
    updateUI(with: data)
}

// ✅ GOOD: Run computation off main thread
@MainActor
func loadData() async {
    let data = await Task.detached {
        heavyComputation()
    }.value
    updateUI(with: data)
}

Use Structured Concurrency

// ❌ BAD: Unstructured tasks can leak
func fetchAllData() {
    Task {
        let users = try await fetchUsers()
    }
    Task {
        let posts = try await fetchPosts()
    }
    // Tasks may outlive this function
}

// ✅ GOOD: Structured with task group
func fetchAllData() async throws -> (users: [User], posts: [Post]) {
    try await withThrowingTaskGroup(of: Void.self) { group in
        var users: [User] = []
        var posts: [Post] = []

        group.addTask {
            users = try await self.fetchUsers()
        }

        group.addTask {
            posts = try await self.fetchPosts()
        }

        try await group.waitForAll()

        return (users, posts)
    }
}

Prioritize Tasks

// ✅ GOOD: Task priorities
Task(priority: .background) {
    await performBackgroundSync()
}

Task(priority: .userInitiated) {
    await loadUserData()
}

Task(priority: .high) {
    await handleUrgentRequest()
}

AsyncStream for Continuous Updates

// ✅ GOOD: AsyncStream for real-time updates
actor NotificationCenter {
    private var continuations: [UUID: AsyncStream<Notification>.Continuation] = [:]

    func notifications() -> AsyncStream<Notification> {
        AsyncStream { continuation in
            let id = UUID()
            continuations[id] = continuation

            continuation.onTermination = { [weak self] _ in
                Task { await self?.removeContinuation(id) }
            }
        }
    }

    func post(_ notification: Notification) {
        for continuation in continuations.values {
            continuation.yield(notification)
        }
    }

    private func removeContinuation(_ id: UUID) {
        continuations.removeValue(forKey: id)
    }
}

// Usage
let notificationCenter = NotificationCenter()

Task {
    for await notification in await notificationCenter.notifications() {
        print("Received: \(notification)")
    }
}

await notificationCenter.post(Notification(name: "test"))

Concurrency Checklist

  • Use async/await instead of completion handlers
  • Mark UI-related code with @MainActor
  • Use actors for mutable shared state
  • Ensure types crossing concurrency boundaries are Sendable
  • Handle task cancellation appropriately
  • Use structured concurrency (task groups) over unstructured tasks
  • Check for race conditions in mutable state
  • Avoid blocking the main thread
  • Set appropriate task priorities
  • Clean up resources on task cancellation

Swift 6 Concurrency Migration

  • Enable strict concurrency checking
  • Fix non-Sendable type warnings
  • Mark appropriate types as @MainActor
  • Replace DispatchQueue with async/await
  • Convert @escaping closures to async functions
  • Use actors instead of locks
  • Audit @unchecked Sendable usage

Resources

Source: SKILL.md on GitHub

No third-party reports yet.

Signed by skilld at 32566aa. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 3 months ago
What it can do
Reads files Network
last_verified
2026-07-16
review_by
2027-06-22
os_version
iOS 27 / macOS 27
All 4 allowed tools
ReadGlobGrepWebFetch

README badge

README badge for rshankras/claude-code-apple-skills/macos