@coralai/sps-cli 0.42.0 → 0.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -3
- package/dist/commands/projectInit.d.ts.map +1 -1
- package/dist/commands/projectInit.js +40 -53
- package/dist/commands/projectInit.js.map +1 -1
- package/dist/commands/skillCommand.d.ts +2 -0
- package/dist/commands/skillCommand.d.ts.map +1 -0
- package/dist/commands/skillCommand.js +235 -0
- package/dist/commands/skillCommand.js.map +1 -0
- package/dist/core/skillStore.d.ts +46 -0
- package/dist/core/skillStore.d.ts.map +1 -0
- package/dist/core/skillStore.js +197 -0
- package/dist/core/skillStore.js.map +1 -0
- package/dist/core/skillStore.test.d.ts +2 -0
- package/dist/core/skillStore.test.d.ts.map +1 -0
- package/dist/core/skillStore.test.js +190 -0
- package/dist/core/skillStore.test.js.map +1 -0
- package/dist/main.js +19 -17
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
- package/skills/architecture-decision-records/SKILL.md +207 -0
- package/skills/backend/SKILL.md +62 -0
- package/skills/backend/references/api-design.md +168 -0
- package/skills/backend/references/caching.md +181 -0
- package/skills/backend/references/data-access.md +173 -0
- package/skills/backend/references/layering.md +181 -0
- package/skills/backend/references/observability.md +190 -0
- package/skills/backend/references/resilience.md +201 -0
- package/skills/backend/references/security.md +186 -0
- package/skills/backend-architect/SKILL.md +119 -0
- package/skills/code-reviewer/SKILL.md +143 -0
- package/skills/coding-standards/SKILL.md +60 -0
- package/skills/coding-standards/references/clean-code.md +258 -0
- package/skills/coding-standards/references/code-review.md +192 -0
- package/skills/coding-standards/references/commits-and-prs.md +226 -0
- package/skills/coding-standards/references/error-strategy.md +193 -0
- package/skills/coding-standards/references/naming.md +185 -0
- package/skills/coding-standards/references/tdd.md +171 -0
- package/skills/database/SKILL.md +53 -0
- package/skills/database/references/indexing.md +190 -0
- package/skills/database/references/migrations.md +199 -0
- package/skills/database/references/nosql.md +185 -0
- package/skills/database/references/queries.md +295 -0
- package/skills/database/references/scaling.md +203 -0
- package/skills/database/references/schema.md +191 -0
- package/skills/database-optimizer/SKILL.md +168 -0
- package/skills/debugging-workflow/SKILL.md +244 -0
- package/skills/devops/SKILL.md +55 -0
- package/skills/devops/references/ci-cd.md +204 -0
- package/skills/devops/references/containers.md +272 -0
- package/skills/devops/references/deploy.md +201 -0
- package/skills/devops/references/iac.md +252 -0
- package/skills/devops/references/observability.md +228 -0
- package/skills/devops/references/secrets.md +178 -0
- package/skills/devops-automator/SKILL.md +164 -0
- package/skills/frontend/SKILL.md +52 -0
- package/skills/frontend/references/accessibility.md +222 -0
- package/skills/frontend/references/components.md +206 -0
- package/skills/frontend/references/performance.md +219 -0
- package/skills/frontend/references/routing.md +209 -0
- package/skills/frontend/references/state.md +190 -0
- package/skills/frontend/references/testing.md +216 -0
- package/skills/frontend-developer/SKILL.md +115 -0
- package/skills/git-workflow/SKILL.md +355 -0
- package/skills/golang/SKILL.md +49 -0
- package/skills/golang/references/concurrency.md +284 -0
- package/skills/golang/references/errors.md +241 -0
- package/skills/golang/references/idioms.md +285 -0
- package/skills/golang/references/testing.md +238 -0
- package/skills/java/SKILL.md +50 -0
- package/skills/java/references/concurrency.md +194 -0
- package/skills/java/references/idioms.md +283 -0
- package/skills/java/references/testing.md +228 -0
- package/skills/kotlin/SKILL.md +47 -0
- package/skills/kotlin/references/coroutines.md +240 -0
- package/skills/kotlin/references/idioms.md +268 -0
- package/skills/kotlin/references/testing.md +219 -0
- package/skills/mobile/SKILL.md +50 -0
- package/skills/mobile/references/architecture.md +204 -0
- package/skills/mobile/references/navigation.md +158 -0
- package/skills/mobile/references/performance.md +152 -0
- package/skills/mobile/references/platform.md +166 -0
- package/skills/mobile/references/state-and-data.md +174 -0
- package/skills/python/SKILL.md +51 -0
- package/skills/python/THIRD_PARTY.md +14 -0
- package/skills/python/references/async.md +218 -0
- package/skills/python/references/error-handling.md +254 -0
- package/skills/python/references/idioms.md +279 -0
- package/skills/python/references/packaging.md +233 -0
- package/skills/python/references/testing.md +269 -0
- package/skills/python/references/typing.md +292 -0
- package/skills/qa-tester/SKILL.md +186 -0
- package/skills/rust/SKILL.md +50 -0
- package/skills/rust/references/async.md +224 -0
- package/skills/rust/references/errors.md +240 -0
- package/skills/rust/references/ownership.md +263 -0
- package/skills/rust/references/testing.md +274 -0
- package/skills/rust/references/traits.md +250 -0
- package/skills/security-engineer/SKILL.md +157 -0
- package/skills/swift/SKILL.md +48 -0
- package/skills/swift/references/concurrency.md +280 -0
- package/skills/swift/references/idioms.md +334 -0
- package/skills/swift/references/testing.md +229 -0
- package/skills/typescript/SKILL.md +51 -0
- package/skills/typescript/references/async.md +241 -0
- package/skills/typescript/references/errors.md +208 -0
- package/skills/typescript/references/idioms.md +246 -0
- package/skills/typescript/references/testing.md +225 -0
- package/skills/typescript/references/tooling.md +208 -0
- package/skills/typescript/references/types.md +259 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# Swift — Concurrency
|
|
2
|
+
|
|
3
|
+
`async/await`, `Task`, actors, `@MainActor`, `AsyncSequence`, cancellation.
|
|
4
|
+
|
|
5
|
+
## `async/await`
|
|
6
|
+
|
|
7
|
+
```swift
|
|
8
|
+
func fetchUser(id: String) async throws -> User {
|
|
9
|
+
let (data, _) = try await URLSession.shared.data(from: url(id))
|
|
10
|
+
return try JSONDecoder().decode(User.self, from: data)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Caller
|
|
14
|
+
let u = try await fetchUser(id: "u1")
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`async` functions suspend at `await` without blocking a thread. `throws` propagates errors.
|
|
18
|
+
|
|
19
|
+
## `Task`
|
|
20
|
+
|
|
21
|
+
`Task` creates a new concurrent context.
|
|
22
|
+
|
|
23
|
+
```swift
|
|
24
|
+
// Fire-and-forget
|
|
25
|
+
Task {
|
|
26
|
+
await sendAnalytics()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Get a result
|
|
30
|
+
let task = Task {
|
|
31
|
+
try await fetchUser(id: "u1")
|
|
32
|
+
}
|
|
33
|
+
let user = try await task.value
|
|
34
|
+
|
|
35
|
+
// Detached — no inherited context, rare
|
|
36
|
+
Task.detached(priority: .background) { await heavyWork() }
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Prefer structured `Task {}` (inherits parent's priority, cancellation, actor). `Task.detached` only when you genuinely need to break the hierarchy.
|
|
40
|
+
|
|
41
|
+
## Structured concurrency — `async let`
|
|
42
|
+
|
|
43
|
+
```swift
|
|
44
|
+
func loadScreen() async throws -> Screen {
|
|
45
|
+
async let user = fetchUser(id: id)
|
|
46
|
+
async let feed = fetchFeed()
|
|
47
|
+
async let prefs = fetchPrefs()
|
|
48
|
+
return Screen(user: try await user, feed: try await feed, prefs: try await prefs)
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
All three requests start concurrently; `await` collects them. If one throws, the others are cancelled.
|
|
53
|
+
|
|
54
|
+
## Task groups — dynamic parallelism
|
|
55
|
+
|
|
56
|
+
```swift
|
|
57
|
+
func loadAll(ids: [String]) async throws -> [User] {
|
|
58
|
+
try await withThrowingTaskGroup(of: User.self) { group in
|
|
59
|
+
for id in ids {
|
|
60
|
+
group.addTask { try await fetchUser(id: id) }
|
|
61
|
+
}
|
|
62
|
+
var users: [User] = []
|
|
63
|
+
for try await u in group { users.append(u) }
|
|
64
|
+
return users
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
On error: cancel the group or rethrow. Siblings complete (or get cancelled if you throw).
|
|
70
|
+
|
|
71
|
+
### Bounded concurrency
|
|
72
|
+
|
|
73
|
+
```swift
|
|
74
|
+
try await withThrowingTaskGroup(of: User.self) { group in
|
|
75
|
+
let limit = 10
|
|
76
|
+
var inFlight = 0
|
|
77
|
+
var iter = ids.makeIterator()
|
|
78
|
+
|
|
79
|
+
while let id = iter.next(), inFlight < limit {
|
|
80
|
+
group.addTask { try await fetchUser(id: id) }
|
|
81
|
+
inFlight += 1
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
var out: [User] = []
|
|
85
|
+
while let u = try await group.next() {
|
|
86
|
+
out.append(u)
|
|
87
|
+
if let id = iter.next() {
|
|
88
|
+
group.addTask { try await fetchUser(id: id) }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return out
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Cancellation
|
|
96
|
+
|
|
97
|
+
Cancellation is cooperative. A cancelled task continues unless it checks.
|
|
98
|
+
|
|
99
|
+
```swift
|
|
100
|
+
func work() async throws {
|
|
101
|
+
for item in bigList {
|
|
102
|
+
try Task.checkCancellation() // throws CancellationError if cancelled
|
|
103
|
+
process(item)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Most stdlib `async` calls check cancellation (`URLSession`, `Task.sleep`). Your own loops must opt in.
|
|
109
|
+
|
|
110
|
+
## Timeouts
|
|
111
|
+
|
|
112
|
+
```swift
|
|
113
|
+
func withTimeout<T>(_ seconds: Double, _ op: @escaping () async throws -> T) async throws -> T {
|
|
114
|
+
try await withThrowingTaskGroup(of: T.self) { group in
|
|
115
|
+
group.addTask { try await op() }
|
|
116
|
+
group.addTask {
|
|
117
|
+
try await Task.sleep(nanoseconds: UInt64(seconds * 1e9))
|
|
118
|
+
throw CancellationError()
|
|
119
|
+
}
|
|
120
|
+
guard let first = try await group.next() else { throw CancellationError() }
|
|
121
|
+
group.cancelAll()
|
|
122
|
+
return first
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
(In Swift 6, `ContinuousClock` / `withTaskGroup`'s timeouts are cleaner.)
|
|
128
|
+
|
|
129
|
+
## Actors — shared mutable state
|
|
130
|
+
|
|
131
|
+
An actor serializes access. Only one method runs at a time; readers wait.
|
|
132
|
+
|
|
133
|
+
```swift
|
|
134
|
+
actor Cache {
|
|
135
|
+
private var data: [String: Data] = [:]
|
|
136
|
+
|
|
137
|
+
func get(_ key: String) -> Data? { data[key] }
|
|
138
|
+
|
|
139
|
+
func set(_ key: String, _ value: Data) {
|
|
140
|
+
data[key] = value
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let cache = Cache()
|
|
145
|
+
let v = await cache.get("u1") // await — crossing actor boundary
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
From outside, calls are `await`; inside the actor, they're synchronous. Don't expose internal mutable state; return values, not references to the actor's storage.
|
|
149
|
+
|
|
150
|
+
## `@MainActor` — UI-thread isolation
|
|
151
|
+
|
|
152
|
+
```swift
|
|
153
|
+
@MainActor
|
|
154
|
+
class ViewModel: ObservableObject {
|
|
155
|
+
@Published var state: State = .loading
|
|
156
|
+
|
|
157
|
+
func load() async {
|
|
158
|
+
state = .loading
|
|
159
|
+
do {
|
|
160
|
+
let u = try await service.fetch() // hops off main for network
|
|
161
|
+
state = .loaded(u) // back on main — safe
|
|
162
|
+
} catch {
|
|
163
|
+
state = .error(error)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Any view model / UI code runs on `MainActor` by default. `await` on non-MainActor code hops off the main thread; results return via `await`, preserving main-thread safety.
|
|
170
|
+
|
|
171
|
+
Individual functions: `@MainActor func update(...) { ... }`.
|
|
172
|
+
|
|
173
|
+
## `Sendable`
|
|
174
|
+
|
|
175
|
+
Types crossing actor / Task boundaries must be `Sendable` — "safe to transfer across concurrency domains".
|
|
176
|
+
|
|
177
|
+
```swift
|
|
178
|
+
struct User: Sendable { ... } // struct of Sendable fields is Sendable
|
|
179
|
+
final class Foo: Sendable { ... } // needs: final, immutable, or internal sync
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Swift 6 enforces Sendable at the compiler level. Common fixes:
|
|
183
|
+
- Make the type a `struct` (value type, usually Sendable automatically).
|
|
184
|
+
- Make reference types `final` and all fields `let` + `Sendable`.
|
|
185
|
+
- For mutable types crossing boundaries, wrap in an `actor`.
|
|
186
|
+
|
|
187
|
+
## `AsyncSequence` — streams
|
|
188
|
+
|
|
189
|
+
```swift
|
|
190
|
+
for try await line in fileHandle.bytes.lines {
|
|
191
|
+
process(line)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Build your own
|
|
195
|
+
struct Counter: AsyncSequence {
|
|
196
|
+
typealias Element = Int
|
|
197
|
+
struct Iterator: AsyncIteratorProtocol {
|
|
198
|
+
var i = 0
|
|
199
|
+
mutating func next() async -> Int? {
|
|
200
|
+
guard i < 10 else { return nil }
|
|
201
|
+
try? await Task.sleep(nanoseconds: 100_000_000)
|
|
202
|
+
defer { i += 1 }
|
|
203
|
+
return i
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
func makeAsyncIterator() -> Iterator { Iterator() }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
for await n in Counter() { print(n) }
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Great for paginated APIs, file reads, WebSocket messages.
|
|
213
|
+
|
|
214
|
+
## Continuations — bridging callback APIs
|
|
215
|
+
|
|
216
|
+
```swift
|
|
217
|
+
func legacy(id: String, completion: @escaping (User?, Error?) -> Void) { ... }
|
|
218
|
+
|
|
219
|
+
func fetch(id: String) async throws -> User {
|
|
220
|
+
try await withCheckedThrowingContinuation { continuation in
|
|
221
|
+
legacy(id: id) { user, error in
|
|
222
|
+
if let user = user { continuation.resume(returning: user) }
|
|
223
|
+
else if let error = error { continuation.resume(throwing: error) }
|
|
224
|
+
else { continuation.resume(throwing: URLError(.unknown)) }
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Call `resume` exactly once.** Calling twice crashes; not calling leaks the coroutine. Use `withCheckedContinuation` during development (crashes on misuse); switch to `withUnsafeContinuation` in release if you need the perf.
|
|
231
|
+
|
|
232
|
+
## Priority & throttling
|
|
233
|
+
|
|
234
|
+
Task priorities: `.userInitiated`, `.userInteractive`, `.utility`, `.background`. Lower priority in background tasks; the scheduler throttles.
|
|
235
|
+
|
|
236
|
+
```swift
|
|
237
|
+
Task(priority: .background) { await indexDatabase() }
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Avoiding blocking
|
|
241
|
+
|
|
242
|
+
Don't call blocking APIs from async code.
|
|
243
|
+
|
|
244
|
+
```swift
|
|
245
|
+
// ❌
|
|
246
|
+
Task { sleep(5); await doThing() }
|
|
247
|
+
|
|
248
|
+
// ✅
|
|
249
|
+
Task { try await Task.sleep(nanoseconds: 5_000_000_000); await doThing() }
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
File I/O: use `URLSession` for network, `FileHandle.bytes` for async reads, or `Task.detached(priority: .utility)` for genuinely blocking calls.
|
|
253
|
+
|
|
254
|
+
## Bridging to / from main thread
|
|
255
|
+
|
|
256
|
+
```swift
|
|
257
|
+
// From background, update UI on main
|
|
258
|
+
Task { @MainActor in
|
|
259
|
+
self.state = .loaded
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Call a MainActor method from non-MainActor
|
|
263
|
+
await viewModel.update()
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Don't use `DispatchQueue.main.async` in new Swift code — `@MainActor` / `Task { @MainActor in ... }` is the native way.
|
|
267
|
+
|
|
268
|
+
## Anti-patterns
|
|
269
|
+
|
|
270
|
+
| Anti-pattern | Fix |
|
|
271
|
+
|---|---|
|
|
272
|
+
| `DispatchSemaphore.wait` to bridge async → sync | Restructure caller as async; semaphores deadlock the runtime |
|
|
273
|
+
| `Task { await ... }.value` from sync code to "await" | Don't; make the caller `async` |
|
|
274
|
+
| `DispatchQueue.main.sync` from background | `await MainActor.run { ... }` or `@MainActor` |
|
|
275
|
+
| Unchecked continuations with complex flow | Use `withCheckedContinuation` during development |
|
|
276
|
+
| Shared mutable struct state across tasks | Use an actor |
|
|
277
|
+
| Detached tasks for everything | Structured `Task {}` inherits context; detached is an escape hatch |
|
|
278
|
+
| Ignoring `Task.isCancelled` / `checkCancellation` in loops | Cancellation is cooperative |
|
|
279
|
+
| `async` on sync functions "for consistency" | `async` has a cost; don't add without reason |
|
|
280
|
+
| Storing `Task` references without cancellation story | Keep a handle; cancel on deinit / view disappearance |
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
# Swift — Idioms
|
|
2
|
+
|
|
3
|
+
Value types, optionals, protocols, closures, `guard`, pattern matching.
|
|
4
|
+
|
|
5
|
+
## `let` / `var`
|
|
6
|
+
|
|
7
|
+
`let` (constant) by default. `var` only when mutating.
|
|
8
|
+
|
|
9
|
+
```swift
|
|
10
|
+
let name = "A"
|
|
11
|
+
var count = 0
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Value vs. reference types
|
|
15
|
+
|
|
16
|
+
- `struct`, `enum`, `tuple` — value types; copied on assignment.
|
|
17
|
+
- `class`, `actor` — reference types; shared.
|
|
18
|
+
|
|
19
|
+
Default to `struct`. Use `class` when you need inheritance (`UIView` subclasses, for framework integration) or shared identity with mutable state. Use `actor` for shared mutable state across concurrency.
|
|
20
|
+
|
|
21
|
+
```swift
|
|
22
|
+
struct User { // value type
|
|
23
|
+
let id: String
|
|
24
|
+
var email: String
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var a = User(id: "u1", email: "a@x.com")
|
|
28
|
+
var b = a // copy
|
|
29
|
+
b.email = "b@x.com" // a unchanged
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Optionals
|
|
33
|
+
|
|
34
|
+
Optional = "value or nothing". Different from `null`.
|
|
35
|
+
|
|
36
|
+
```swift
|
|
37
|
+
var name: String? = nil
|
|
38
|
+
name.count // ❌ compile error
|
|
39
|
+
|
|
40
|
+
// Unwrap
|
|
41
|
+
if let n = name {
|
|
42
|
+
print(n.count)
|
|
43
|
+
}
|
|
44
|
+
guard let n = name else { return } // unwrap or exit scope
|
|
45
|
+
print(n.count)
|
|
46
|
+
|
|
47
|
+
name?.count // optional chaining → Int?
|
|
48
|
+
name ?? "unknown" // nil-coalescing
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`!` is a compile-time assertion that the value is non-nil. Crash at runtime if wrong. Avoid in normal code.
|
|
52
|
+
|
|
53
|
+
## `guard`
|
|
54
|
+
|
|
55
|
+
Fail-fast at the top of a function; flat code after.
|
|
56
|
+
|
|
57
|
+
```swift
|
|
58
|
+
func process(user: User?) {
|
|
59
|
+
guard let user = user, user.active else { return }
|
|
60
|
+
// user is now non-optional and active
|
|
61
|
+
work(user)
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Prefer `guard` over nested `if let`s. It keeps the happy path at the outermost scope.
|
|
66
|
+
|
|
67
|
+
## Structs with behaviour
|
|
68
|
+
|
|
69
|
+
Structs can have methods, computed properties, and satisfy protocols.
|
|
70
|
+
|
|
71
|
+
```swift
|
|
72
|
+
struct Email {
|
|
73
|
+
let raw: String
|
|
74
|
+
var isValid: Bool { raw.contains("@") }
|
|
75
|
+
func normalized() -> Email { Email(raw: raw.lowercased()) }
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Enums with associated values
|
|
80
|
+
|
|
81
|
+
Swift enums carry data. Discriminated unions, like Rust.
|
|
82
|
+
|
|
83
|
+
```swift
|
|
84
|
+
enum Result<T> {
|
|
85
|
+
case success(T)
|
|
86
|
+
case failure(Error)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
switch result {
|
|
90
|
+
case .success(let value):
|
|
91
|
+
use(value)
|
|
92
|
+
case .failure(let error):
|
|
93
|
+
log(error)
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Enums are exhaustive — the compiler forces every case. Add a case → every `switch` fails until updated.
|
|
98
|
+
|
|
99
|
+
## Protocols — small, composable
|
|
100
|
+
|
|
101
|
+
```swift
|
|
102
|
+
protocol Identifiable { var id: String { get } }
|
|
103
|
+
protocol Timestamped { var createdAt: Date { get } }
|
|
104
|
+
|
|
105
|
+
struct User: Identifiable, Timestamped {
|
|
106
|
+
let id: String
|
|
107
|
+
let createdAt: Date
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Protocol extensions — default implementations
|
|
112
|
+
|
|
113
|
+
```swift
|
|
114
|
+
protocol Named { var name: String { get } }
|
|
115
|
+
|
|
116
|
+
extension Named {
|
|
117
|
+
func greet() -> String { "Hello \(name)" }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
struct User: Named { let name: String }
|
|
121
|
+
User(name: "A").greet() // "Hello A"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Default methods via extensions. Implementers can override.
|
|
125
|
+
|
|
126
|
+
### Existentials vs. generics
|
|
127
|
+
|
|
128
|
+
```swift
|
|
129
|
+
// Existential — heterogeneous collection
|
|
130
|
+
func renderAll(_ items: [any Renderable]) { ... }
|
|
131
|
+
|
|
132
|
+
// Generic — homogeneous; faster (static dispatch)
|
|
133
|
+
func renderAll<T: Renderable>(_ items: [T]) { ... }
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Swift 5.6+ requires `any` keyword for existentials. Prefer generics unless you need heterogeneity.
|
|
137
|
+
|
|
138
|
+
## Closures
|
|
139
|
+
|
|
140
|
+
```swift
|
|
141
|
+
let double: (Int) -> Int = { $0 * 2 }
|
|
142
|
+
|
|
143
|
+
users.map { $0.email }
|
|
144
|
+
users.filter { $0.active }
|
|
145
|
+
|
|
146
|
+
users.sorted { $0.name < $1.name }
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Trailing closure syntax is idiomatic. Name parameters when the closure is longer than one line:
|
|
150
|
+
|
|
151
|
+
```swift
|
|
152
|
+
users.sorted { a, b in
|
|
153
|
+
a.name.compare(b.name, options: .caseInsensitive) == .orderedAscending
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## `@escaping` closures
|
|
158
|
+
|
|
159
|
+
When a closure is stored or called after the function returns, it must be `@escaping`.
|
|
160
|
+
|
|
161
|
+
```swift
|
|
162
|
+
func load(onComplete: @escaping (Result<User, Error>) -> Void) { ... }
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Inside an `@escaping` closure, `self` must be explicit — which is the language's way of nudging you to think about retain cycles.
|
|
166
|
+
|
|
167
|
+
```swift
|
|
168
|
+
load { [weak self] result in
|
|
169
|
+
guard let self else { return }
|
|
170
|
+
self.update(result)
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
For new code, prefer `async` over callbacks whenever possible.
|
|
175
|
+
|
|
176
|
+
## Extensions
|
|
177
|
+
|
|
178
|
+
Add methods to existing types without subclassing.
|
|
179
|
+
|
|
180
|
+
```swift
|
|
181
|
+
extension String {
|
|
182
|
+
func toSlug() -> String {
|
|
183
|
+
lowercased().replacingOccurrences(of: " ", with: "-")
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
"Hello World".toSlug() // "hello-world"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Organize by feature. Common pattern:
|
|
191
|
+
|
|
192
|
+
```swift
|
|
193
|
+
// File: User+Formatting.swift
|
|
194
|
+
extension User { func displayName() -> String { ... } }
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Don't add methods with generic names (`.isValid`) that collide across extensions.
|
|
198
|
+
|
|
199
|
+
## Pattern matching
|
|
200
|
+
|
|
201
|
+
Beyond `switch` on enum cases:
|
|
202
|
+
|
|
203
|
+
```swift
|
|
204
|
+
let pair = (1, 2)
|
|
205
|
+
switch pair {
|
|
206
|
+
case (0, 0): print("origin")
|
|
207
|
+
case (_, 0): print("x-axis")
|
|
208
|
+
case (0, _): print("y-axis")
|
|
209
|
+
case (x, y) where x == y: print("diagonal")
|
|
210
|
+
case let (x, y): print("(\(x), \(y))")
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// if case / for case
|
|
214
|
+
if case .success(let v) = result { use(v) }
|
|
215
|
+
for case .success(let v) in results { use(v) }
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Use `where` clauses inside `switch` for guards.
|
|
219
|
+
|
|
220
|
+
## Error handling — throwing functions
|
|
221
|
+
|
|
222
|
+
```swift
|
|
223
|
+
enum ValidationError: Error {
|
|
224
|
+
case emptyEmail
|
|
225
|
+
case invalidAge(Int)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
func validate(user: User) throws {
|
|
229
|
+
guard !user.email.isEmpty else { throw ValidationError.emptyEmail }
|
|
230
|
+
guard user.age >= 0 else { throw ValidationError.invalidAge(user.age) }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
do {
|
|
234
|
+
try validate(user: u)
|
|
235
|
+
} catch ValidationError.emptyEmail {
|
|
236
|
+
show("email required")
|
|
237
|
+
} catch let e as ValidationError {
|
|
238
|
+
show("invalid: \(e)")
|
|
239
|
+
} catch {
|
|
240
|
+
log.error("unexpected: \(error)")
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`try?` → `Optional`, `try!` → crash on error (tests only).
|
|
245
|
+
|
|
246
|
+
## `Result` type
|
|
247
|
+
|
|
248
|
+
For async callbacks and stored outcomes.
|
|
249
|
+
|
|
250
|
+
```swift
|
|
251
|
+
func fetch(_ url: URL) async -> Result<Data, URLError> { ... }
|
|
252
|
+
|
|
253
|
+
switch await fetch(url) {
|
|
254
|
+
case .success(let data): use(data)
|
|
255
|
+
case .failure(let e): log(e)
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
With `async throws`, you usually don't need `Result` — throws flows naturally.
|
|
260
|
+
|
|
261
|
+
## Access control
|
|
262
|
+
|
|
263
|
+
| Level | Reach |
|
|
264
|
+
|---|---|
|
|
265
|
+
| `private` | Declaring scope only |
|
|
266
|
+
| `fileprivate` | Whole file |
|
|
267
|
+
| `internal` (default) | Module |
|
|
268
|
+
| `public` | Cross-module, not subclassable |
|
|
269
|
+
| `open` | Cross-module, subclassable |
|
|
270
|
+
|
|
271
|
+
Default to most-restrictive. Libraries: prefer `public` for API surface; `open` only when subclassing is a designed extension point.
|
|
272
|
+
|
|
273
|
+
## Property wrappers
|
|
274
|
+
|
|
275
|
+
Encapsulate storage with behaviour: `@Published`, `@State`, `@AppStorage`, `@UserDefault`.
|
|
276
|
+
|
|
277
|
+
```swift
|
|
278
|
+
@propertyWrapper
|
|
279
|
+
struct Clamped<V: Comparable> {
|
|
280
|
+
var wrappedValue: V {
|
|
281
|
+
didSet { wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound) }
|
|
282
|
+
}
|
|
283
|
+
let range: ClosedRange<V>
|
|
284
|
+
init(wrappedValue: V, _ range: ClosedRange<V>) {
|
|
285
|
+
self.range = range
|
|
286
|
+
self.wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
struct Config { @Clamped(1...10) var threads: Int = 4 }
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Used heavily in SwiftUI; write your own sparingly.
|
|
294
|
+
|
|
295
|
+
## Strings
|
|
296
|
+
|
|
297
|
+
- `String` — Unicode-correct by default. Subscripting requires `Index`, not `Int`.
|
|
298
|
+
- `Character` — a grapheme cluster (may be multiple code points).
|
|
299
|
+
- Use `.count` for grapheme count (slow-ish); `.unicodeScalars.count` for code points.
|
|
300
|
+
|
|
301
|
+
```swift
|
|
302
|
+
let s = "héllo"
|
|
303
|
+
s.count // 5
|
|
304
|
+
s[s.startIndex] // "h"
|
|
305
|
+
|
|
306
|
+
// Index arithmetic
|
|
307
|
+
let i = s.index(s.startIndex, offsetBy: 2)
|
|
308
|
+
s[i] // "l"
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Avoid `.utf8.count` unless you're measuring bytes for serialization.
|
|
312
|
+
|
|
313
|
+
## Collections — lazy vs. eager
|
|
314
|
+
|
|
315
|
+
```swift
|
|
316
|
+
users.map { $0.email } // eager, allocates
|
|
317
|
+
users.lazy.map { $0.email }.filter { $0.contains("@x") }.first(where: ...)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Use `.lazy` for chained operations on large collections where you only need a subset.
|
|
321
|
+
|
|
322
|
+
## Anti-patterns
|
|
323
|
+
|
|
324
|
+
| Anti-pattern | Fix |
|
|
325
|
+
|---|---|
|
|
326
|
+
| `!` force-unwrap | Use `if let` / `guard let` / `??` |
|
|
327
|
+
| `try!` outside tests | `try` + error handling |
|
|
328
|
+
| Force-casting `as!` | Use `as?` and handle `nil`; test types you trust |
|
|
329
|
+
| `[String: Any]` in public API | Model a type |
|
|
330
|
+
| Class for a pure data type | Use `struct` |
|
|
331
|
+
| Storing closures without `[weak self]` | Retain cycle; always think capture semantics |
|
|
332
|
+
| `print()` for logging | `Logger` (`os.Logger`) in new code |
|
|
333
|
+
| `NSString` / `NSArray` in Swift code | Use native types; bridge at Objective-C boundary |
|
|
334
|
+
| `Error` protocol conformance without `LocalizedError` for user-facing errors | Implement `errorDescription` if you surface it in UI |
|