swift-code-reviewer-skill 1.2.0 → 1.3.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/CHANGELOG.md +43 -169
- package/README.md +43 -2
- package/SKILL.md +194 -711
- package/bin/install.js +1 -1
- package/package.json +2 -1
- package/references/agent-loop-feedback.md +148 -0
- package/references/companion-skills.md +70 -0
- package/references/spec-adherence.md +157 -0
- package/skills/README.md +43 -0
- package/skills/swift-concurrency/NOTICE.md +18 -0
- package/skills/swift-concurrency/SKILL.md +235 -0
- package/skills/swift-concurrency/references/actors.md +640 -0
- package/skills/swift-concurrency/references/async-await-basics.md +249 -0
- package/skills/swift-concurrency/references/async-sequences.md +635 -0
- package/skills/swift-concurrency/references/core-data.md +533 -0
- package/skills/swift-concurrency/references/glossary.md +96 -0
- package/skills/swift-concurrency/references/linting.md +38 -0
- package/skills/swift-concurrency/references/memory-management.md +542 -0
- package/skills/swift-concurrency/references/migration.md +721 -0
- package/skills/swift-concurrency/references/performance.md +574 -0
- package/skills/swift-concurrency/references/sendable.md +578 -0
- package/skills/swift-concurrency/references/tasks.md +604 -0
- package/skills/swift-concurrency/references/testing.md +565 -0
- package/skills/swift-concurrency/references/threading.md +452 -0
- package/skills/swift-expert/NOTICE.md +18 -0
- package/skills/swift-expert/SKILL.md +226 -0
- package/skills/swift-expert/references/async-concurrency.md +363 -0
- package/skills/swift-expert/references/memory-performance.md +380 -0
- package/skills/swift-expert/references/protocol-oriented.md +357 -0
- package/skills/swift-expert/references/swiftui-patterns.md +294 -0
- package/skills/swift-expert/references/testing-patterns.md +402 -0
- package/skills/swift-testing/NOTICE.md +18 -0
- package/skills/swift-testing/SKILL.md +295 -0
- package/skills/swift-testing/references/async-testing.md +245 -0
- package/skills/swift-testing/references/dump-snapshot-testing.md +265 -0
- package/skills/swift-testing/references/fixtures.md +193 -0
- package/skills/swift-testing/references/integration-testing.md +189 -0
- package/skills/swift-testing/references/migration-xctest.md +301 -0
- package/skills/swift-testing/references/parameterized-tests.md +171 -0
- package/skills/swift-testing/references/snapshot-testing.md +201 -0
- package/skills/swift-testing/references/test-doubles.md +243 -0
- package/skills/swift-testing/references/test-organization.md +231 -0
- package/skills/swiftui-expert-skill/NOTICE.md +18 -0
- package/skills/swiftui-expert-skill/SKILL.md +281 -0
- package/skills/swiftui-expert-skill/references/accessibility-patterns.md +151 -0
- package/skills/swiftui-expert-skill/references/animation-advanced.md +403 -0
- package/skills/swiftui-expert-skill/references/animation-basics.md +284 -0
- package/skills/swiftui-expert-skill/references/animation-transitions.md +326 -0
- package/skills/swiftui-expert-skill/references/charts-accessibility.md +135 -0
- package/skills/swiftui-expert-skill/references/charts.md +602 -0
- package/skills/swiftui-expert-skill/references/image-optimization.md +203 -0
- package/skills/swiftui-expert-skill/references/latest-apis.md +464 -0
- package/skills/swiftui-expert-skill/references/layout-best-practices.md +266 -0
- package/skills/swiftui-expert-skill/references/liquid-glass.md +414 -0
- package/skills/swiftui-expert-skill/references/list-patterns.md +394 -0
- package/skills/swiftui-expert-skill/references/macos-scenes.md +318 -0
- package/skills/swiftui-expert-skill/references/macos-views.md +357 -0
- package/skills/swiftui-expert-skill/references/macos-window-styling.md +303 -0
- package/skills/swiftui-expert-skill/references/performance-patterns.md +403 -0
- package/skills/swiftui-expert-skill/references/scroll-patterns.md +293 -0
- package/skills/swiftui-expert-skill/references/sheet-navigation-patterns.md +363 -0
- package/skills/swiftui-expert-skill/references/state-management.md +417 -0
- package/skills/swiftui-expert-skill/references/view-structure.md +389 -0
- package/skills/swiftui-ui-patterns/NOTICE.md +18 -0
- package/skills/swiftui-ui-patterns/SKILL.md +95 -0
- package/skills/swiftui-ui-patterns/references/app-wiring.md +201 -0
- package/skills/swiftui-ui-patterns/references/async-state.md +96 -0
- package/skills/swiftui-ui-patterns/references/components-index.md +50 -0
- package/skills/swiftui-ui-patterns/references/controls.md +57 -0
- package/skills/swiftui-ui-patterns/references/deeplinks.md +66 -0
- package/skills/swiftui-ui-patterns/references/focus.md +90 -0
- package/skills/swiftui-ui-patterns/references/form.md +97 -0
- package/skills/swiftui-ui-patterns/references/grids.md +71 -0
- package/skills/swiftui-ui-patterns/references/haptics.md +71 -0
- package/skills/swiftui-ui-patterns/references/input-toolbar.md +51 -0
- package/skills/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
- package/skills/swiftui-ui-patterns/references/list.md +86 -0
- package/skills/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
- package/skills/swiftui-ui-patterns/references/macos-settings.md +71 -0
- package/skills/swiftui-ui-patterns/references/matched-transitions.md +59 -0
- package/skills/swiftui-ui-patterns/references/media.md +73 -0
- package/skills/swiftui-ui-patterns/references/menu-bar.md +101 -0
- package/skills/swiftui-ui-patterns/references/navigationstack.md +159 -0
- package/skills/swiftui-ui-patterns/references/overlay.md +45 -0
- package/skills/swiftui-ui-patterns/references/performance.md +62 -0
- package/skills/swiftui-ui-patterns/references/previews.md +48 -0
- package/skills/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
- package/skills/swiftui-ui-patterns/references/scrollview.md +87 -0
- package/skills/swiftui-ui-patterns/references/searchable.md +71 -0
- package/skills/swiftui-ui-patterns/references/sheets.md +155 -0
- package/skills/swiftui-ui-patterns/references/split-views.md +72 -0
- package/skills/swiftui-ui-patterns/references/tabview.md +114 -0
- package/skills/swiftui-ui-patterns/references/theming.md +71 -0
- package/skills/swiftui-ui-patterns/references/title-menus.md +93 -0
- package/skills/swiftui-ui-patterns/references/top-bar.md +49 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
# Async/Await Concurrency
|
|
2
|
+
|
|
3
|
+
> Reference for: Swift Expert
|
|
4
|
+
> Load when: async/await, actors, structured concurrency, Task management
|
|
5
|
+
|
|
6
|
+
## Async/Await Basics
|
|
7
|
+
|
|
8
|
+
```swift
|
|
9
|
+
// Async function
|
|
10
|
+
func fetchUser(id: Int) async throws -> User {
|
|
11
|
+
let url = URL(string: "https://api.example.com/users/\(id)")!
|
|
12
|
+
let (data, _) = try await URLSession.shared.data(from: url)
|
|
13
|
+
return try JSONDecoder().decode(User.self, from: data)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Calling async functions
|
|
17
|
+
func loadUserData() async {
|
|
18
|
+
do {
|
|
19
|
+
let user = try await fetchUser(id: 123)
|
|
20
|
+
print("Loaded: \(user.name)")
|
|
21
|
+
} catch {
|
|
22
|
+
print("Error: \(error)")
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Multiple concurrent operations
|
|
27
|
+
func fetchMultipleUsers(ids: [Int]) async throws -> [User] {
|
|
28
|
+
try await withThrowingTaskGroup(of: User.self) { group in
|
|
29
|
+
for id in ids {
|
|
30
|
+
group.addTask {
|
|
31
|
+
try await fetchUser(id: id)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
var users: [User] = []
|
|
36
|
+
for try await user in group {
|
|
37
|
+
users.append(user)
|
|
38
|
+
}
|
|
39
|
+
return users
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Actors
|
|
45
|
+
|
|
46
|
+
```swift
|
|
47
|
+
// Actor for thread-safe state management
|
|
48
|
+
actor UserCache {
|
|
49
|
+
private var cache: [Int: User] = [:]
|
|
50
|
+
private var inProgress: [Int: Task<User, Error>] = [:]
|
|
51
|
+
|
|
52
|
+
func user(id: Int) async throws -> User {
|
|
53
|
+
// Check cache first
|
|
54
|
+
if let cached = cache[id] {
|
|
55
|
+
return cached
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check if already loading
|
|
59
|
+
if let task = inProgress[id] {
|
|
60
|
+
return try await task.value
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Start new load
|
|
64
|
+
let task = Task {
|
|
65
|
+
try await fetchUser(id: id)
|
|
66
|
+
}
|
|
67
|
+
inProgress[id] = task
|
|
68
|
+
|
|
69
|
+
do {
|
|
70
|
+
let user = try await task.value
|
|
71
|
+
cache[id] = user
|
|
72
|
+
inProgress.removeValue(forKey: id)
|
|
73
|
+
return user
|
|
74
|
+
} catch {
|
|
75
|
+
inProgress.removeValue(forKey: id)
|
|
76
|
+
throw error
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
func clearCache() {
|
|
81
|
+
cache.removeAll()
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Usage
|
|
86
|
+
let cache = UserCache()
|
|
87
|
+
let user = try await cache.user(id: 123)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## MainActor
|
|
91
|
+
|
|
92
|
+
```swift
|
|
93
|
+
// UI updates must happen on main thread
|
|
94
|
+
@MainActor
|
|
95
|
+
class ViewModel: ObservableObject {
|
|
96
|
+
@Published var users: [User] = []
|
|
97
|
+
@Published var isLoading = false
|
|
98
|
+
|
|
99
|
+
func loadUsers() async {
|
|
100
|
+
isLoading = true
|
|
101
|
+
defer { isLoading = false }
|
|
102
|
+
|
|
103
|
+
do {
|
|
104
|
+
// This async work happens off main thread
|
|
105
|
+
let loadedUsers = try await fetchMultipleUsers(ids: [1, 2, 3])
|
|
106
|
+
|
|
107
|
+
// Property updates happen on main thread automatically
|
|
108
|
+
users = loadedUsers
|
|
109
|
+
} catch {
|
|
110
|
+
print("Error: \(error)")
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Isolated functions
|
|
116
|
+
@MainActor
|
|
117
|
+
func updateUI() {
|
|
118
|
+
// This always runs on main thread
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Non-isolated functions in MainActor type
|
|
122
|
+
@MainActor
|
|
123
|
+
class DataManager {
|
|
124
|
+
var data: [String] = []
|
|
125
|
+
|
|
126
|
+
// Runs on main thread
|
|
127
|
+
func updateData(_ newData: [String]) {
|
|
128
|
+
data = newData
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Can run on any thread
|
|
132
|
+
nonisolated func processData(_ input: String) -> String {
|
|
133
|
+
return input.uppercased()
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Structured Concurrency
|
|
139
|
+
|
|
140
|
+
```swift
|
|
141
|
+
// Task groups for dynamic concurrency
|
|
142
|
+
func downloadImages(urls: [URL]) async throws -> [UIImage] {
|
|
143
|
+
try await withThrowingTaskGroup(of: (Int, UIImage).self) { group in
|
|
144
|
+
for (index, url) in urls.enumerated() {
|
|
145
|
+
group.addTask {
|
|
146
|
+
let (data, _) = try await URLSession.shared.data(from: url)
|
|
147
|
+
guard let image = UIImage(data: data) else {
|
|
148
|
+
throw ImageError.invalidData
|
|
149
|
+
}
|
|
150
|
+
return (index, image)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
var images = [UIImage?](repeating: nil, count: urls.count)
|
|
155
|
+
for try await (index, image) in group {
|
|
156
|
+
images[index] = image
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return images.compactMap { $0 }
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Parallel async-let
|
|
164
|
+
func loadDashboard() async throws -> Dashboard {
|
|
165
|
+
async let user = fetchUser(id: currentUserID)
|
|
166
|
+
async let posts = fetchPosts()
|
|
167
|
+
async let notifications = fetchNotifications()
|
|
168
|
+
|
|
169
|
+
return try await Dashboard(
|
|
170
|
+
user: user,
|
|
171
|
+
posts: posts,
|
|
172
|
+
notifications: notifications
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Task Management
|
|
178
|
+
|
|
179
|
+
```swift
|
|
180
|
+
// Detached tasks
|
|
181
|
+
func backgroundWork() {
|
|
182
|
+
Task.detached(priority: .background) {
|
|
183
|
+
// Runs independently, doesn't inherit context
|
|
184
|
+
await performHeavyComputation()
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Cancellation
|
|
189
|
+
class DataLoader {
|
|
190
|
+
private var loadTask: Task<Void, Never>?
|
|
191
|
+
|
|
192
|
+
func startLoading() {
|
|
193
|
+
loadTask?.cancel()
|
|
194
|
+
|
|
195
|
+
loadTask = Task {
|
|
196
|
+
do {
|
|
197
|
+
for try await item in itemStream() {
|
|
198
|
+
// Check for cancellation
|
|
199
|
+
try Task.checkCancellation()
|
|
200
|
+
|
|
201
|
+
await process(item)
|
|
202
|
+
|
|
203
|
+
// Alternative cancellation check
|
|
204
|
+
if Task.isCancelled {
|
|
205
|
+
break
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} catch is CancellationError {
|
|
209
|
+
print("Task cancelled")
|
|
210
|
+
} catch {
|
|
211
|
+
print("Error: \(error)")
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
func stopLoading() {
|
|
217
|
+
loadTask?.cancel()
|
|
218
|
+
loadTask = nil
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Task priorities
|
|
223
|
+
Task(priority: .high) {
|
|
224
|
+
await criticalWork()
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
Task(priority: .low) {
|
|
228
|
+
await backgroundWork()
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## AsyncSequence
|
|
233
|
+
|
|
234
|
+
```swift
|
|
235
|
+
// Custom AsyncSequence
|
|
236
|
+
struct NumberSequence: AsyncSequence {
|
|
237
|
+
typealias Element = Int
|
|
238
|
+
let range: Range<Int>
|
|
239
|
+
|
|
240
|
+
struct AsyncIterator: AsyncIteratorProtocol {
|
|
241
|
+
var current: Int
|
|
242
|
+
let end: Int
|
|
243
|
+
|
|
244
|
+
mutating func next() async -> Int? {
|
|
245
|
+
guard current < end else { return nil }
|
|
246
|
+
|
|
247
|
+
// Simulate async work
|
|
248
|
+
try? await Task.sleep(for: .milliseconds(100))
|
|
249
|
+
|
|
250
|
+
defer { current += 1 }
|
|
251
|
+
return current
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
func makeAsyncIterator() -> AsyncIterator {
|
|
256
|
+
AsyncIterator(current: range.lowerBound, end: range.upperBound)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Usage
|
|
261
|
+
for await number in NumberSequence(range: 0..<10) {
|
|
262
|
+
print(number)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Async stream
|
|
266
|
+
func eventStream() -> AsyncStream<Event> {
|
|
267
|
+
AsyncStream { continuation in
|
|
268
|
+
let observer = NotificationCenter.default.addObserver(
|
|
269
|
+
forName: .eventOccurred,
|
|
270
|
+
object: nil,
|
|
271
|
+
queue: nil
|
|
272
|
+
) { notification in
|
|
273
|
+
if let event = notification.object as? Event {
|
|
274
|
+
continuation.yield(event)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
continuation.onTermination = { _ in
|
|
279
|
+
NotificationCenter.default.removeObserver(observer)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Sendable Protocol
|
|
286
|
+
|
|
287
|
+
```swift
|
|
288
|
+
// Sendable types can be safely passed across concurrency domains
|
|
289
|
+
struct User: Sendable {
|
|
290
|
+
let id: Int
|
|
291
|
+
let name: String
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Non-Sendable by default (has mutable state)
|
|
295
|
+
class ViewModel {
|
|
296
|
+
var data: [String] = []
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Make it Sendable with @unchecked (use carefully!)
|
|
300
|
+
class SafeViewModel: @unchecked Sendable {
|
|
301
|
+
private let lock = NSLock()
|
|
302
|
+
private var _data: [String] = []
|
|
303
|
+
|
|
304
|
+
var data: [String] {
|
|
305
|
+
lock.lock()
|
|
306
|
+
defer { lock.unlock() }
|
|
307
|
+
return _data
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
func setData(_ newData: [String]) {
|
|
311
|
+
lock.lock()
|
|
312
|
+
defer { lock.unlock() }
|
|
313
|
+
_data = newData
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Generic with Sendable constraint
|
|
318
|
+
func processData<T: Sendable>(_ data: T) async -> T {
|
|
319
|
+
// Can safely pass data across concurrency boundaries
|
|
320
|
+
await Task.detached {
|
|
321
|
+
return data
|
|
322
|
+
}.value
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Continuations
|
|
327
|
+
|
|
328
|
+
```swift
|
|
329
|
+
// Bridging callback-based APIs to async/await
|
|
330
|
+
func fetchDataAsync() async throws -> Data {
|
|
331
|
+
try await withCheckedThrowingContinuation { continuation in
|
|
332
|
+
fetchDataWithCallback { result in
|
|
333
|
+
switch result {
|
|
334
|
+
case .success(let data):
|
|
335
|
+
continuation.resume(returning: data)
|
|
336
|
+
case .failure(let error):
|
|
337
|
+
continuation.resume(throwing: error)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Unsafe continuations for performance-critical code
|
|
344
|
+
func unsafeFetchDataAsync() async -> Data {
|
|
345
|
+
await withUnsafeContinuation { continuation in
|
|
346
|
+
fetchDataWithCallback { data in
|
|
347
|
+
continuation.resume(returning: data)
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Best Practices
|
|
354
|
+
|
|
355
|
+
- Use actors for mutable shared state
|
|
356
|
+
- Prefer async/await over completion handlers
|
|
357
|
+
- Use MainActor for UI-related code
|
|
358
|
+
- Leverage structured concurrency (task groups, async-let)
|
|
359
|
+
- Check for cancellation in long-running tasks
|
|
360
|
+
- Mark types as Sendable when safe
|
|
361
|
+
- Use continuations to bridge legacy async code
|
|
362
|
+
- Avoid blocking in async contexts
|
|
363
|
+
- Use Task.detached sparingly (breaks structured concurrency)
|