ios-swift-concurrency
Use when implementing async/await, Task management, actors, or Combine reactive patterns in iOS applications.
What this skill does
# iOS - Swift Concurrency
Modern concurrency patterns using async/await, actors, and structured concurrency in Swift.
## Key Concepts
### Async/Await Fundamentals
```swift
// 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 APIError.invalidResponse
}
return try JSONDecoder().decode(User.self, from: data)
}
// Calling async functions
func loadUserProfile() async {
do {
let user = try await fetchUser(id: "123")
await MainActor.run {
updateUI(with: user)
}
} catch {
await MainActor.run {
showError(error)
}
}
}
```
### Task Management
```swift
class UserViewController: UIViewController {
private var loadTask: Task<Void, Never>?
override func viewDidLoad() {
super.viewDidLoad()
// Create a task for async work
loadTask = Task {
await loadUserData()
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
// Cancel when view disappears
loadTask?.cancel()
}
private func loadUserData() async {
// Check for cancellation
guard !Task.isCancelled else { return }
do {
let user = try await fetchUser()
// Check again before UI update
guard !Task.isCancelled else { return }
await MainActor.run {
displayUser(user)
}
} catch {
// Handle error
}
}
}
```
### Actors for Thread Safety
```swift
actor UserCache {
private var cache: [String: User] = [:]
func user(for id: String) -> User? {
cache[id]
}
func setUser(_ user: User, for id: String) {
cache[id] = user
}
func clear() {
cache.removeAll()
}
}
// Usage
let cache = UserCache()
Task {
await cache.setUser(user, for: user.id)
let cached = await cache.user(for: "123")
}
```
### MainActor for UI Updates
```swift
@MainActor
class UserViewModel: ObservableObject {
@Published var user: User?
@Published var isLoading = false
@Published var error: Error?
func loadUser(id: String) async {
isLoading = true
defer { isLoading = false }
do {
user = try await userService.fetchUser(id: id)
} catch {
self.error = error
}
}
}
// Or use MainActor.run for specific operations
func fetchAndDisplay() async {
let data = await fetchData()
await MainActor.run {
self.displayData(data)
}
}
```
## Best Practices
### Structured Concurrency with TaskGroup
```swift
func fetchAllUserData(userId: String) async throws -> UserProfile {
async let user = fetchUser(id: userId)
async let posts = fetchPosts(userId: userId)
async let followers = fetchFollowers(userId: userId)
// All three requests run concurrently
return try await UserProfile(
user: user,
posts: posts,
followers: followers
)
}
// For dynamic number of tasks
func fetchMultipleUsers(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
}
}
```
### AsyncSequence for Streams
```swift
// Custom async sequence
struct NotificationStream: AsyncSequence {
typealias Element = Notification
let name: Notification.Name
struct AsyncIterator: AsyncIteratorProtocol {
let name: Notification.Name
var iterator: AsyncStream<Notification>.Iterator
mutating func next() async -> Notification? {
await iterator.next()
}
}
func makeAsyncIterator() -> AsyncIterator {
let stream = AsyncStream<Notification> { continuation in
let observer = NotificationCenter.default.addObserver(
forName: name,
object: nil,
queue: nil
) { notification in
continuation.yield(notification)
}
continuation.onTermination = { _ in
NotificationCenter.default.removeObserver(observer)
}
}
return AsyncIterator(name: name, iterator: stream.makeAsyncIterator())
}
}
// Usage
for await notification in NotificationStream(name: .userDidLogin) {
handleLogin(notification)
}
```
### Continuations for Callback-Based APIs
```swift
func fetchLegacyData() async throws -> Data {
try await withCheckedThrowingContinuation { continuation in
legacyAPI.fetch { result in
switch result {
case .success(let data):
continuation.resume(returning: data)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
// For delegate-based APIs
class LocationManager: NSObject, CLLocationManagerDelegate {
private var locationContinuation: CheckedContinuation<CLLocation, Error>?
func getCurrentLocation() async throws -> CLLocation {
try await withCheckedThrowingContinuation { continuation in
self.locationContinuation = continuation
locationManager.requestLocation()
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
locationContinuation?.resume(returning: locations[0])
locationContinuation = nil
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
locationContinuation?.resume(throwing: error)
locationContinuation = nil
}
}
```
## Common Patterns
### Cancellation Handling
```swift
func downloadFile(url: URL) async throws -> Data {
var data = Data()
let (bytes, _) = try await URLSession.shared.bytes(from: url)
for try await byte in bytes {
// Cooperative cancellation check
try Task.checkCancellation()
data.append(byte)
}
return data
}
```
### Debouncing with Task
```swift
class SearchViewModel: ObservableObject {
@Published var searchText = ""
@Published var results: [SearchResult] = []
private var searchTask: Task<Void, Never>?
func search(_ query: String) {
searchTask?.cancel()
searchTask = Task {
// Debounce delay
try? await Task.sleep(for: .milliseconds(300))
guard !Task.isCancelled else { return }
do {
let results = try await searchService.search(query: query)
guard !Task.isCancelled else { return }
await MainActor.run {
self.results = results
}
} catch {
// Handle error
}
}
}
}
```
### Combine Integration
```swift
import Combine
extension Publisher {
func asyncMap<T>(_ transform: @escaping (Output) async -> T) -> AnyPublisher<T, Failure> {
flatMap { value in
Future { promise in
Task {
let result = await transform(value)
promise(.success(result))
}
}
}
.eraseToAnyPublisher()
}
}
// Convert async function to publisher
func userPublisher(id: String) -> AnyPublisher<User, Error> {
Future { promise in
Task {
do {
let user = try await fetchUser(id: id)
promise(.success(user))
} catch {
promise(.failure(error))
Related in Productivity
gitea-workflow
IncludedOrchestrate agile development workflows for Gitea repositories using the tea CLI. Use when working with Gitea-hosted repos and asking to 'run the workflow', 'continue working', 'what's next', 'complete the task cycle', 'start my day', 'end the sprint', 'implement the next task', or wanting guided step-by-step development assistance. Keywords: workflow, orchestrate, agile, task cycle, sprint, daily, implement, review, PR, standup, retrospective, gitea, tea.
microsoft-graph-gateway
IncludedRoute Microsoft Graph work in this workspace. Use when users want to read or write Outlook mail, calendar events, contacts, OneDrive or SharePoint files, Teams, Planner, To Do, users, groups, directory data, or arbitrary Microsoft Graph endpoints from VS Code. Prefer WorkIQ for common read scenarios. Use Microsoft Graph for write actions and gap-read scenarios that need exact Graph properties, filters, permissions, or endpoints.
copilotkit
IncludedUse when building with CopilotKit — setup, development, integrations, debugging, upgrading, or contributing. Routes to the appropriate specialized skill based on the task.
wordly-wisdom
IncludedProvides calibrated decision analysis using Charlie Munger-style multiple mental models, inversion, incentive mapping, circle-of-competence checks, misjudgment audits, second-order effects, and forecast updates. Use when the user asks for an oracle take, a hard call, a decision memo, a premortem, an outside view, a red-team, a sanity-check, what am I missing, think this through, or wants a strategy, hire, investment, plan, product, partnership, or major life choice analysed. Avoid for simple factual lookups or time-sensitive legal, medical, or market questions without fresh evidence.
swain-session
IncludedSession management and project status dashboard. Owns the full session lifecycle (start/work/close/resume), focus lane, bookmarks, worktree detection, and tab naming. Also serves as the project status dashboard — shows active epics, progress, actionable next steps, blocked items, tasks, GitHub issues, and recommendations. Worktree creation is deferred to swain-do task dispatch (SPEC-195). Triggers on: 'session', 'status', 'what's next', 'dashboard', 'overview', 'where are we', 'what should I work on', 'show me priorities', 'bookmark', 'focus on', 'session info'.
gandi
IncludedComprehensive Gandi domain registrar integration for domain and DNS management. Register and manage domains, create/update/delete DNS records (A, AAAA, CNAME, MX, TXT, SRV, and more), configure email forwarding and aliases, check SSL certificate status, create DNS snapshots for safe rollback, bulk update zone files, and monitor domain expiration. Supports multi-domain management, zone file import/export, and automated DNS backups. Includes both read-only and destructive operations with safety controls.