Skip to main content

swift-patterns

Swift/iOS: SwiftUI, Combine, async/await, actors, SPM, Core Data, UIKit interop. Triggers: Swift, SwiftUI, Combine, iOS, Xcode, actor, Core Data, @MainActor, @State.

Quellinformationen

Repository
softspark/ai-toolkit
Letzte Quellaktivität
6. Mai 2026 um 09:48
Erkannte Sprache von SKILL.md
Englisch
Sterne
179
Forks
21

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
2 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
swift-patterns
description
Swift/iOS: SwiftUI, Combine, async/await, actors, SPM, Core Data, UIKit interop. Triggers: Swift, SwiftUI, Combine, iOS, Xcode, actor, Core Data, @MainActor, @State.
effort
medium
user-invocable
false
allowed-tools
Read
# Swift / iOS Patterns ## Project Structure ### Swift Package (SPM) ``` MyPackage/ ├── Package.swift ├── Sources/ │ ├── MyLibrary/ │ └── MyExecutable/ ├── Tests/ │ └── MyLibraryTests/ └── Plugins/ ``` ```swift // swift-tools-version: 5.10 import PackageDescription let package = Package( name: "MyPackage", platforms: [.iOS(.v17), .macOS(.v14)], products: [ .library(name: "MyLibrary", targets: ["MyLibrary"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0"), ], targets: [ .target(name: "MyLibrary", dependencies: [.product(name: "Algorithms", package: "swift-algorithms")]), .testTarget(name: "MyLibraryTests", dependencies: ["MyLibrary"]), ] ) ``` ### Xcode Project Layout ``` MyApp/ ├── MyApp/ │ ├── App/ # Entry point, ContentView │ ├── Features/ # Feature modules (Views, ViewModels, Models) │ ├── Core/ # Networking, Storage, Extensions │ └── Resources/ # Assets.xcassets, Info.plist ├── MyAppTests/ └── MyAppUITests/ ``` --- ## Idioms / Code Style ### Optionals ```swift // guard-let for early exit func process(user: User?) { guard let user else { return } print(user.name) } // Optional chaining + nil coalescing let name = user?.profile?.displayName ?? "Anonymous" // map/flatMap on optionals let length: Int? = optionalString.map { $0.count } // Never force-unwrap in production: user!.name ``` ### Protocol-Oriented Programming ```swift protocol Cacheable: Identifiable where ID: Hashable { var cacheKey: String { get } } extension Cacheable where ID == String { var cacheKey: String { id } } ``` ### Value Types vs Reference Types Use structs by default (value semantics, thread-safe). Use classes when identity matters, shared mutable state is intentional, inheritance is needed, or ObjC interop is required. ### Property Wrappers ```swift @propertyWrapper struct Clamped<Value: Comparable> { var wrappedValue: Value { didSet { wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound) } } let range: ClosedRange<Value> init(wrappedValue: Value, _ range: ClosedRange<Value>) { self.range = range self.wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound) } } struct Volume { @Clamped(0...100) var level: Int = 50 } ``` ### Result Builders ```swift @resultBuilder struct ArrayBuilder<Element> { static func buildBlock(_ components: [Element]...) -> [Element] { components.flatMap { $0 } } static func buildExpression(_ expression: Element) -> [Element] { [expression] } static func buildOptional(_ component: [Element]?) -> [Element] { component ?? [] } } ``` --- ## Error Handling ### throws / try / catch ```swift enum NetworkError: Error, LocalizedError { case invalidURL case timeout(seconds: Int) case serverError(statusCode: Int) var errorDescription: String? { switch self { case .invalidURL: "Invalid URL." case .timeout(let s): "Timed out after \(s)s." case .serverError(let code): "Server returned \(code)." } } } do { let user = try fetchUser(id: "123") } catch let error as NetworkError { handleNetworkError(error) } catch { handleUnexpected(error) } let user = try? fetchUser(id: "123") // nil on error ``` ### Result Type ```swift switch fetchData(from: url) { case .success(let data): process(data) case .failure(let error): showError(error) } ``` ### Typed Throws (Swift 6) and Async Throws ```swift func load() throws(DatabaseError) -> [Item] { /* compiler-enforced error type */ } func fetchUser(id: String) async throws -> User { let (data, response) = try await URLSession.shared.data(from: url) guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { throw NetworkError.serverError(statusCode: 0) } return try JSONDecoder().decode(User.self, from: data) } ``` --- ## Testing ### XCTest ```swift final class UserServiceTests: XCTestCase { var sut: UserService! var mockRepo: MockUserRepository! override func setUp() { mockRepo = MockUserRepository() sut = UserService(repository: mockRepo) } func testFetchUser_success() async throws { mockRepo.stubbedUser = User(id: "1", name: "Alice") let user = try await sut.fetchUser(id: "1") XCTAssertEqual(user.name, "Alice") } func testFetchUser_notFound_throws() async { mockRepo.shouldFail = true do { _ = try await sut.fetchUser(id: "999") XCTFail("Expected error") } catch { XCTAssertTrue(error is UserService.Error) } } } ``` ### Swift Testing Framework (Swift 6+) ```swift import Testing @Suite("UserService") struct UserServiceTests { @Test("fetches user by ID") func fetchUser() async throws { let mockRepo = MockUserRepository() mockRepo.stubbedUser = User(id: "1", name: "Alice") let sut = UserService(repository: mockRepo) let user = try await sut.fetchUser(id: "1") #expect(user.name == "Alice") } @Test("throws on missing user", arguments: ["999", ""]) func fetchMissingUser(id: String) async { let sut = UserService(repository: MockUserRepository()) await #expect(throws: UserService.Error.self) { try await sut.fetchUser(id: id) } } } ``` ### Protocol-Based Mocking ```swift protocol UserRepository { func fetch(id: String) async throws -> User } final class MockUserRepository: UserRepository { var stubbedUser: User? var shouldFail = false private(set) var fetchCallCount = 0 func fetch(id: String) async throws -> User { fetchCallCount += 1 if shouldFail { throw NSError(domain: "", code: 0) } return stubbedUser ?? User(id: id, name: "Default") } } ``` ### UI Testing ```swift func testLoginFlow() { let app = XCUIApplication() app.launchArguments = ["--uitesting"] app.launch() app.textFields["email"].tap() app.textFields["email"].typeText("user@example.com") app.secureTextFields["password"].typeText("pass") app.buttons["Sign In"].tap() XCTAssertTrue(app.staticTexts["Welcome"].waitForExistence(timeout: 5)) } ``` --- ## Common Frameworks For SwiftUI + `@Observable`, Combine, Structured Concurrency, SwiftData, and Vapor framework patterns with complete code examples, see [reference/frameworks.md](reference/frameworks.md). --- ## Performance ### Copy-on-Write Array, String, Dictionary use COW automatically. For custom value types: ```swift struct LargeData { private final class Storage { var buffer: [UInt8]; init(_ b: [UInt8]) { buffer = b } } private var storage: Storage var buffer: [UInt8] { get { storage.buffer } set { if !isKnownUniquelyReferenced(&storage) { storage = Storage(newValue) } else { storage.buffer = newValue } } } } ``` ### ARC Retain Cycles ```swift // weak — closure may outlive self service.fetch { [weak self] result in guard let self else { return } self.update(with: result) } // unowned — self guaranteed to outlive closure lazy var tick: () -> Void = { [unowned self] in self.count += 1 } ``` ### Sendable and Actors ```swift struct Config: Sendable { let apiURL: URL; let timeout: TimeInterval } actor ImageCache { private var cache: [URL: Data] = [:] func image(for url: URL) -> Data? { cache[url] } func store(_ data: Data, for url: URL) { cache[url] = data } } ``` ### Instruments | Instrument | Use For | |---|---| | Time Profiler | CPU bottlenecks | | Allocations | Memory growth | | Leaks | Retain cycles | | SwiftUI | View body re-evaluations | | Core Animation | FPS, offscreen rendering | Profile on device (not simulator). Use `os_signpost` for custom spans. --- ## Build / Package Management ### SPM Commands ```bash swift package resolve # Resolve deps swift build -c release # Release build swift test --filter MyTests # Filtered test run ``` ### xcconfig ``` // Shared.xcconfig SWIFT_VERSION = 5.10 IPHONEOS_DEPLOYMENT_TARGET = 17.0 SWIFT_STRICT_CONCURRENCY = complete // Debug.xcconfig #include "Shared.xcconfig" SWIFT_OPTIMIZATION_LEVEL = -Onone SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG // Release.xcconfig #include "Shared.xcconfig" SWIFT_OPTIMIZATION_LEVEL = -O SWIFT_COMPILATION_MODE = wholemodule ``` ### Tuist Define targets in `Project.swift` using `ProjectDescription`. Map each app, framework, and test target with `bundleId`, `sources`, and `dependencies`. Use `.external(name:)` for SPM deps and `.target(name:)` for internal. Schemes: separate Debug/Release/Testing. Enable ASan + TSan in test schemes. --- ## Anti-Patterns | Anti-Pattern | Problem | Fix | |---|---|---| | Force unwrap `!` | Runtime crash | `guard let`, `if let`, `??` | | Massive view controller | Untestable | MVVM, composable views | | Stringly-typed APIs | No compiler checks | Enums, phantom types | | Ignoring `@MainActor` | Off-main-thread UI | Annotate view models | | Retain cycles | Memory leaks | `[weak self]` / `[unowned self]` | | Blocking main thread | UI freezes | `async/await`, `Task { }` | | `UserDefaults` for secrets | Insecure | Keychain (`SecItemAdd`) | | `@ObservedObject` for owned state | Object recreated | `@StateObject` or `@State` + `@Observable` | ## Rules - **MUST** use Swift concurrency primitives (`async/await`, actors, `Task`) for new code — GCD is legacy and mixes poorly with the new model - **MUST** annotate view models with `@MainActor` when they touch UI state — off-main mutations cause runtime warnings and flaky UI - **NEVER** force-unwrap (`!`) without a documented invariant in a comment; runtime crashes from unwrap are the top iOS crash category - **NEVER** store secrets in `UserDefaults` or plist — use Keychain APIs (`SecItemAdd`, `SecItemCopyMatching`) - **CRITICAL**: SwiftUI state flows downward; mutations flow through `@State`, `@Binding`, or `@Observable`. Never mutate a parent's state from a child via a captured reference — it breaks dependency tracking. - **MANDATORY**: every closure that captures `self` inside a reference type uses `[weak self]` or `[unowned self]` — retain cycles are the top memory-leak cause ## Gotchas - `@StateObject` and `@ObservedObject` look similar but behave oppositely on parent re-render: `@StateObject` persists, `@ObservedObject` may re-initialize. Using `@ObservedObject` for view-owned state recreates the object on every render — state loss without error. - `Task { @MainActor in ... }` inside a non-`@MainActor` context does **not** synchronously return to main; it schedules. Code between the `await` and `Task` boundary runs on whatever actor you came from, which can race with UI updates. - `AsyncStream` continuations without `onTermination` leak: if the consumer cancels, the producer keeps yielding forever. Always install a termination handler. - SwiftData `@Query` with `@Environment(\.modelContext)` invalidates on every write; heavy reads in a watched view cause perf drops. Use `@FetchRequest`-style fetch descriptors with explicit refresh, not ambient `@Query`, for large datasets. - Combine's `.receive(on: DispatchQueue.main)` schedules asynchronously — if the next operator expects sync execution, order matters. Prefer moving `.receive(on:)` to just before the sink, not mid-pipeline. - Swift Concurrency does not compose cleanly with Objective-C completion handlers; `withCheckedContinuation` bridges but a continuation that is never resumed hangs the Task forever. Always pair resumes with all control-flow paths, including errors. ## When NOT to Load - For **Flutter or React Native** cross-platform code — use `/flutter-patterns` or JS patterns; this skill is Swift-only - For generic iOS architecture decisions (MVC vs MVVM vs VIPER) — use `/architecture-decision` - For Kotlin-based cross-platform mobile (KMP) — use `/kotlin-patterns` - For mobile CI/CD specifics (TestFlight, Fastlane) — use `/ci-cd-patterns` - For Objective-C interop deep dives — outside scope; this skill focuses on modern Swift
Auf GitHub ansehen