npx claudepluginhub caphtech/claude-marketplace --plugin mobile-pluginWant just this skill?
Then install: npx claudepluginhub u/[userId]/[slug]
Swift Concurrency支援。async/await、Actor、Sendable、データ競合防止。 使用タイミング: (1) 並行処理コードの実装時、(2) Swift 6 Strict Concurrency対応時、 (3) データ競合の診断・修正時、(4) MainActorとバックグラウンド処理の設計時
This skill uses the workspace's default tool permissions.
references/actor-patterns.mdreferences/concurrency-patterns.mdreferences/task-patterns.mdSwift Concurrency 支援スキル
Swift Concurrencyの正しい使用法とデータ競合防止をガイドする。
対象
- async/await パターン
- Actor と隔離
- Sendable 準拠
- Task と構造化並行性
- @MainActor とUI更新
Swift 6 Strict Concurrency
Sendable要件
// ✅ 値型は自動的にSendable
struct UserData: Sendable {
let id: UUID
let name: String
}
// ✅ Actorは暗黙的にSendable
actor DataManager {
private var cache: [String: Data] = [:]
func getData(for key: String) -> Data? {
cache[key]
}
}
// ⚠️ クラスは明示的な対応が必要
// 方法1: @unchecked Sendable(内部で同期を保証)
final class ThreadSafeCache: @unchecked Sendable {
private let lock = NSLock()
private var storage: [String: Any] = [:]
func get(_ key: String) -> Any? {
lock.withLock { storage[key] }
}
}
// 方法2: 不変クラス
final class ImmutableConfig: Sendable {
let apiURL: URL
let timeout: TimeInterval
init(apiURL: URL, timeout: TimeInterval) {
self.apiURL = apiURL
self.timeout = timeout
}
}
Actor隔離
// グローバルActor定義
@globalActor
actor DatabaseActor {
static let shared = DatabaseActor()
}
// Actor隔離されたクラス
@DatabaseActor
class DatabaseService {
private var connection: DatabaseConnection?
func query(_ sql: String) async throws -> [Row] {
// DatabaseActor上で実行される
guard let conn = connection else {
throw DatabaseError.notConnected
}
return try await conn.execute(sql)
}
}
// MainActorへの切り替え
@DatabaseActor
class ViewModel {
private var data: [Item] = []
func loadData() async {
let items = try? await fetchItems()
// UI更新はMainActorで
await MainActor.run {
self.updateUI(with: items ?? [])
}
}
@MainActor
private func updateUI(with items: [Item]) {
// UI更新処理
}
}
async/await パターン
基本パターン
// 非同期関数
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)
}
// 呼び出し
func loadUserProfile() async {
do {
let user = try await fetchUser(id: "123")
await MainActor.run {
self.user = user
}
} catch {
await MainActor.run {
self.error = error
}
}
}
並列実行
// async let で並列実行
func loadDashboard() async throws -> Dashboard {
async let user = fetchUser()
async let posts = fetchPosts()
async let notifications = fetchNotifications()
// すべて並列で実行され、結果を待つ
return try await Dashboard(
user: user,
posts: posts,
notifications: notifications
)
}
// TaskGroupで動的な並列処理
func fetchAllUsers(ids: [String]) async throws -> [User] {
try await withThrowingTaskGroup(of: User.self) { group in
for id in ids {
group.addTask {
try await self.fetchUser(id: id)
}
}
var users: [User] = []
for try await user in group {
users.append(user)
}
return users
}
}
Task管理
構造化並行性
class SearchViewModel: ObservableObject {
@Published var results: [SearchResult] = []
@Published var isSearching = false
private var searchTask: Task<Void, Never>?
func search(query: String) {
// 前の検索をキャンセル
searchTask?.cancel()
searchTask = Task {
await MainActor.run {
isSearching = true
}
// デバウンス
try? await Task.sleep(for: .milliseconds(300))
// キャンセルチェック
guard !Task.isCancelled else { return }
do {
let results = try await performSearch(query)
guard !Task.isCancelled else { return }
await MainActor.run {
self.results = results
self.isSearching = false
}
} catch {
guard !Task.isCancelled else { return }
await MainActor.run {
self.isSearching = false
}
}
}
}
deinit {
searchTask?.cancel()
}
}
優先度とdetached Task
// 優先度指定
Task(priority: .userInitiated) {
await loadCriticalData()
}
Task(priority: .background) {
await performBackgroundSync()
}
// detached Task(親のコンテキストを継承しない)
Task.detached(priority: .utility) {
await self.cleanupCache()
}
よくあるエラーと解決策
1. Sendable違反
// ❌ エラー: Capture of 'self' with non-sendable type
class DataLoader {
var data: [String] = []
func load() {
Task {
self.data = await fetchData() // エラー
}
}
}
// ✅ 解決策1: Actorにする
actor DataLoader {
var data: [String] = []
func load() {
Task {
self.data = await fetchData() // OK
}
}
}
// ✅ 解決策2: @MainActorを使う
@MainActor
class DataLoader {
var data: [String] = []
func load() {
Task {
self.data = await fetchData() // OK
}
}
}
2. Actor再入問題
actor BankAccount {
var balance: Int = 0
// ⚠️ awaitの前後でbalanceが変わる可能性
func transferTo(_ other: BankAccount, amount: Int) async {
guard balance >= amount else { return }
balance -= amount // ここで中断
await other.deposit(amount) // 他のタスクがbalanceを変更可能
// 戻ってきた時、想定外の状態かも
}
// ✅ トランザクション的に処理
func withdraw(_ amount: Int) -> Bool {
guard balance >= amount else { return false }
balance -= amount
return true
}
func deposit(_ amount: Int) {
balance += amount
}
}
// 呼び出し側で制御
func transfer(from: BankAccount, to: BankAccount, amount: Int) async {
let withdrawn = await from.withdraw(amount)
if withdrawn {
await to.deposit(amount)
}
}
3. MainActor隔離
// ❌ バックグラウンドからUI更新
func loadData() async {
let data = try? await fetchData()
self.items = data // MainActorでない場合エラー
}
// ✅ 明示的にMainActorで実行
func loadData() async {
let data = try? await fetchData()
await MainActor.run {
self.items = data
}
}
// ✅ またはプロパティをMainActorに
@MainActor
var items: [Item] = []
チェックリスト
設計時
- 共有状態を持つクラスはActorにすべきか検討
- Sendable要件を満たす型設計か
- MainActorの範囲は適切か
実装時
- async letで並列化できる箇所はあるか
- Taskのキャンセル処理を実装したか
- await前後での状態変化を考慮したか
レビュー時
- @unchecked Sendableの使用は正当化されているか
- nonisolated(unsafe)の使用は避けられているか
- 循環参照やメモリリークはないか
Similar Skills
Expert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.