Use when implementing async/await, Task management, actors, or Combine reactive patterns in iOS applications.
Implements async/await, actors, and Task management for modern iOS concurrency. Use when converting callback-based APIs, handling concurrent operations, or ensuring thread-safe data access in Swift apps.
/plugin marketplace add TheBushidoCollective/han/plugin install jutsu-go@hanThis skill is limited to using the following tools:
Modern concurrency patterns using async/await, actors, and structured concurrency in 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)
}
}
}
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
}
}
}
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
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)
}
}
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
}
}
// 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)
}
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
}
}
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
}
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
}
}
}
}
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))
}
}
}
.eraseToAnyPublisher()
}
Bad:
// DON'T do this
func loadData() {
let semaphore = DispatchSemaphore(value: 0)
Task {
data = await fetchData()
semaphore.signal()
}
semaphore.wait() // Blocks main thread!
}
Good:
func loadData() async {
data = await fetchData()
}
Bad:
func processItems(_ items: [Item]) async {
for item in items {
await process(item) // Never checks cancellation
}
}
Good:
func processItems(_ items: [Item]) async throws {
for item in items {
try Task.checkCancellation()
await process(item)
}
}
Bad:
class Counter {
var count = 0 // Not thread-safe!
func increment() {
count += 1
}
}
Good:
actor Counter {
var count = 0
func increment() {
count += 1
}
}
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.