Skip to main content

swift-patterns

SwiftUI view composition, @Observable patterns, async/await concurrency, TCA architecture, and Combine reactive streams.

소스 정보

저장소
vibeeval/vibecosystem
최근 소스 활동
2026년 6월 10일 14:11
감지된 SKILL.md 언어
영어
스타
538
포크
47

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
swift-patterns
description
SwiftUI view composition, @Observable patterns, async/await concurrency, TCA architecture, and Combine reactive streams.
# Swift Patterns Modern Swift patterns for iOS/macOS application development. ## SwiftUI View Composition ```swift // Small, focused views composed together struct ProductCard: View { let product: Product var body: some View { VStack(alignment: .leading, spacing: 8) { ProductImage(url: product.imageURL) ProductInfo(name: product.name, price: product.price) RatingStars(rating: product.rating, count: product.reviewCount) } .padding() .background(.regularMaterial) .clipShape(RoundedRectangle(cornerRadius: 12)) } } // Extract subviews for readability and reusability struct ProductImage: View { let url: URL var body: some View { AsyncImage(url: url) { phase in switch phase { case .success(let image): image.resizable().aspectRatio(contentMode: .fill) case .failure: Image(systemName: "photo").foregroundStyle(.secondary) case .empty: ProgressView() @unknown default: EmptyView() } } .frame(height: 200) .clipped() } } // ViewModifier for reusable styling struct CardModifier: ViewModifier { func body(content: Content) -> some View { content .padding() .background(.regularMaterial) .clipShape(RoundedRectangle(cornerRadius: 12)) .shadow(radius: 2) } } extension View { func cardStyle() -> some View { modifier(CardModifier()) } } ``` ## @Observable Pattern (iOS 17+) ```swift import Observation @Observable final class ProductStore { var products: [Product] = [] var isLoading = false var errorMessage: String? private let apiClient: APIClient init(apiClient: APIClient = .shared) { self.apiClient = apiClient } func loadProducts() async { isLoading = true errorMessage = nil do { products = try await apiClient.fetchProducts() } catch { errorMessage = error.localizedDescription } isLoading = false } func deleteProduct(_ product: Product) async throws { try await apiClient.deleteProduct(id: product.id) products.removeAll { $0.id == product.id } } } // Usage in SwiftUI (automatic tracking, no @Published needed) struct ProductListView: View { @State private var store = ProductStore() var body: some View { List(store.products) { product in ProductCard(product: product) } .overlay { if store.isLoading { ProgressView() } if let error = store.errorMessage { ContentUnavailableView("Error", systemImage: "exclamationmark.triangle", description: Text(error)) } } .task { await store.loadProducts() } } } ``` ## Structured Concurrency ```swift // TaskGroup for parallel async work func loadDashboard() async throws -> Dashboard { async let profile = apiClient.fetchProfile() async let orders = apiClient.fetchRecentOrders() async let recommendations = apiClient.fetchRecommendations() // All three run concurrently, await all results return try await Dashboard( profile: profile, orders: orders, recommendations: recommendations ) } // TaskGroup with dynamic number of tasks func loadImages(urls: [URL]) async -> [URL: UIImage] { await withTaskGroup(of: (URL, UIImage?).self) { group in for url in urls { group.addTask { let image = try? await ImageLoader.load(url) return (url, image) } } var results: [URL: UIImage] = [:] for await (url, image) in group { if let image { results[url] = image } } return results } } // Actor for thread-safe shared state actor ImageCache { private var cache: [URL: UIImage] = [:] private var inFlight: [URL: Task<UIImage, Error>] = [:] func image(for url: URL) async throws -> UIImage { if let cached = cache[url] { return cached } // Coalesce duplicate requests if let existing = inFlight[url] { return try await existing.value } let task = Task { let (data, _) = try await URLSession.shared.data(from: url) guard let image = UIImage(data: data) else { throw ImageError.invalidData } return image } inFlight[url] = task let image = try await task.value cache[url] = image inFlight[url] = nil return image } } ``` ## TCA (The Composable Architecture) Pattern ```swift import ComposableArchitecture @Reducer struct ProductFeature { @ObservableState struct State: Equatable { var products: [Product] = [] var isLoading = false var alert: AlertState<Action>? } enum Action { case onAppear case productsLoaded(Result<[Product], Error>) case deleteProduct(Product) case alertDismissed } @Dependency(\.apiClient) var apiClient var body: some ReducerOf<Self> { Reduce { state, action in switch action { case .onAppear: state.isLoading = true return .run { send in let result = await Result { try await apiClient.fetchProducts() } await send(.productsLoaded(result)) } case .productsLoaded(.success(let products)): state.isLoading = false state.products = products return .none case .productsLoaded(.failure(let error)): state.isLoading = false state.alert = AlertState { TextState(error.localizedDescription) } return .none case .deleteProduct(let product): state.products.removeAll { $0.id == product.id } return .run { _ in try await apiClient.deleteProduct(id: product.id) } case .alertDismissed: state.alert = nil return .none } } } } ``` ## Checklist - [ ] Views under 50 lines; extract subviews for composition - [ ] Use @Observable (iOS 17+) over ObservableObject/@Published - [ ] Structured concurrency with async let for parallel work - [ ] Actors for shared mutable state (not locks/queues) - [ ] ViewModifiers for reusable styling patterns - [ ] Environment for dependency injection in SwiftUI - [ ] Task cancellation handled (check Task.isCancelled) - [ ] Preview providers for every view with mock data ## Anti-Patterns - Massive views: 200+ line body property (extract subviews) - @StateObject in child views: use @State or pass as parameter - Blocking main thread with synchronous work in views - Force unwrapping optionals: use guard let or nil coalescing - Ignoring task cancellation: leaked work after view disappears - Using singletons instead of dependency injection (untestable)
GitHub에서 보기