with one click
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.
// Swift/iOS: SwiftUI, Combine, async/await, actors, SPM, Core Data, UIKit interop. Triggers: Swift, SwiftUI, Combine, iOS, Xcode, actor, Core Data, @MainActor, @State.
[HINT] Download the complete skill directory including SKILL.md and all related files
| 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 |
MyPackage/
āāā Package.swift
āāā Sources/
ā āāā MyLibrary/
ā āāā MyExecutable/
āāā Tests/
ā āāā MyLibraryTests/
āāā Plugins/
// 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"]),
]
)
MyApp/
āāā MyApp/
ā āāā App/ # Entry point, ContentView
ā āāā Features/ # Feature modules (Views, ViewModels, Models)
ā āāā Core/ # Networking, Storage, Extensions
ā āāā Resources/ # Assets.xcassets, Info.plist
āāā MyAppTests/
āāā MyAppUITests/
// 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 Cacheable: Identifiable where ID: Hashable {
var cacheKey: String { get }
}
extension Cacheable where ID == String {
var cacheKey: String { id }
}
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.
@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
}
@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 ?? [] }
}
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
switch fetchData(from: url) {
case .success(let data): process(data)
case .failure(let error): showError(error)
}
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)
}
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)
}
}
}
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 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")
}
}
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))
}
For SwiftUI + @Observable, Combine, Structured Concurrency, SwiftData, and Vapor framework patterns with complete code examples, see reference/frameworks.md.
Array, String, Dictionary use COW automatically. For custom value types:
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 }
}
}
}
// 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 }
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 }
}
| 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.
swift package resolve # Resolve deps
swift build -c release # Release build
swift test --filter MyTests # Filtered test run
// 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
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-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 |
async/await, actors, Task) for new code ā GCD is legacy and mixes poorly with the new model@MainActor when they touch UI state ā off-main mutations cause runtime warnings and flaky UI!) without a documented invariant in a comment; runtime crashes from unwrap are the top iOS crash categoryUserDefaults or plist ā use Keychain APIs (SecItemAdd, SecItemCopyMatching)@State, @Binding, or @Observable. Never mutate a parent's state from a child via a captured reference ā it breaks dependency tracking.self inside a reference type uses [weak self] or [unowned self] ā retain cycles are the top memory-leak cause@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.@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..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.withCheckedContinuation bridges but a continuation that is never resumed hangs the Task forever. Always pair resumes with all control-flow paths, including errors./flutter-patterns or JS patterns; this skill is Swift-only/architecture-decision/kotlin-patterns/ci-cd-patterns