opencodekit 0.16.15 → 0.16.17
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/dist/index.js +1 -1
- package/dist/template/.opencode/AGENTS.md +1 -1
- package/dist/template/.opencode/agent/plan.md +77 -161
- package/dist/template/.opencode/command/create.md +75 -307
- package/dist/template/.opencode/command/design.md +53 -589
- package/dist/template/.opencode/command/handoff.md +76 -180
- package/dist/template/.opencode/command/init.md +45 -211
- package/dist/template/.opencode/command/plan.md +62 -514
- package/dist/template/.opencode/command/pr.md +56 -226
- package/dist/template/.opencode/command/research.md +55 -266
- package/dist/template/.opencode/command/resume.md +33 -138
- package/dist/template/.opencode/command/review-codebase.md +54 -202
- package/dist/template/.opencode/command/ship.md +78 -127
- package/dist/template/.opencode/command/start.md +47 -577
- package/dist/template/.opencode/command/status.md +55 -354
- package/dist/template/.opencode/command/ui-review.md +52 -298
- package/dist/template/.opencode/command/verify.md +36 -250
- package/dist/template/.opencode/memory.db-shm +0 -0
- package/dist/template/.opencode/memory.db-wal +0 -0
- package/dist/template/.opencode/plugin/README.md +8 -4
- package/dist/template/.opencode/plugin/swarm-enforcer.ts +182 -27
- package/dist/template/.opencode/skill/augment-context-engine/SKILL.md +112 -0
- package/dist/template/.opencode/skill/augment-context-engine/mcp.json +6 -0
- package/dist/template/.opencode/skill/core-data-expert/SKILL.md +82 -0
- package/dist/template/.opencode/skill/core-data-expert/references/batch-operations.md +543 -0
- package/dist/template/.opencode/skill/core-data-expert/references/cloudkit-integration.md +259 -0
- package/dist/template/.opencode/skill/core-data-expert/references/concurrency.md +522 -0
- package/dist/template/.opencode/skill/core-data-expert/references/fetch-requests.md +643 -0
- package/dist/template/.opencode/skill/core-data-expert/references/glossary.md +233 -0
- package/dist/template/.opencode/skill/core-data-expert/references/migration.md +393 -0
- package/dist/template/.opencode/skill/core-data-expert/references/model-configuration.md +597 -0
- package/dist/template/.opencode/skill/core-data-expert/references/performance.md +300 -0
- package/dist/template/.opencode/skill/core-data-expert/references/persistent-history.md +553 -0
- package/dist/template/.opencode/skill/core-data-expert/references/project-audit.md +60 -0
- package/dist/template/.opencode/skill/core-data-expert/references/saving.md +574 -0
- package/dist/template/.opencode/skill/core-data-expert/references/stack-setup.md +625 -0
- package/dist/template/.opencode/skill/core-data-expert/references/testing.md +300 -0
- package/dist/template/.opencode/skill/core-data-expert/references/threading.md +589 -0
- package/dist/template/.opencode/skill/swift-concurrency/SKILL.md +246 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/actors.md +640 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/async-algorithms.md +822 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/async-await-basics.md +249 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/async-sequences.md +670 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/core-data.md +533 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/glossary.md +128 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/linting.md +142 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/memory-management.md +542 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/migration.md +1076 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/performance.md +574 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/sendable.md +578 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/tasks.md +604 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/testing.md +565 -0
- package/dist/template/.opencode/skill/swift-concurrency/references/threading.md +452 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/SKILL.md +290 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/animation-advanced.md +351 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/animation-basics.md +284 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/animation-transitions.md +326 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/image-optimization.md +286 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/layout-best-practices.md +312 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/liquid-glass.md +377 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/list-patterns.md +153 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/modern-apis.md +400 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/performance-patterns.md +377 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/scroll-patterns.md +305 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/sheet-navigation-patterns.md +292 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/state-management.md +447 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/text-formatting.md +285 -0
- package/dist/template/.opencode/skill/swiftui-expert-skill/references/view-structure.md +276 -0
- package/package.json +1 -1
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
# Threading and Concurrency
|
|
2
|
+
|
|
3
|
+
Core Data threading rules are strict but essential for data integrity. This guide covers safe multi-threading patterns, common pitfalls, and debugging techniques.
|
|
4
|
+
|
|
5
|
+
## The Golden Rule
|
|
6
|
+
|
|
7
|
+
**Never pass `NSManagedObject` instances between threads. Always use `NSManagedObjectID`.**
|
|
8
|
+
|
|
9
|
+
```swift
|
|
10
|
+
// ❌ WRONG: Passing object between contexts
|
|
11
|
+
let article = viewContext.object(...)
|
|
12
|
+
backgroundContext.perform {
|
|
13
|
+
article.name = "Updated" // CRASH!
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// ✅ CORRECT: Pass object ID
|
|
17
|
+
let objectID = article.objectID
|
|
18
|
+
backgroundContext.perform {
|
|
19
|
+
guard let article = try? backgroundContext.existingObject(with: objectID) as? Article else { return }
|
|
20
|
+
article.name = "Updated" // Safe!
|
|
21
|
+
try? backgroundContext.save()
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Why NSManagedObjectID is Thread-Safe
|
|
26
|
+
|
|
27
|
+
`NSManagedObjectID` is immutable and thread-safe. It's a unique identifier that works across contexts and threads.
|
|
28
|
+
|
|
29
|
+
```swift
|
|
30
|
+
// Object IDs are thread-safe
|
|
31
|
+
let objectID: NSManagedObjectID = article.objectID
|
|
32
|
+
|
|
33
|
+
// Can be passed to any thread/context
|
|
34
|
+
DispatchQueue.global().async {
|
|
35
|
+
let context = container.newBackgroundContext()
|
|
36
|
+
context.perform {
|
|
37
|
+
if let article = try? context.existingObject(with: objectID) as? Article {
|
|
38
|
+
// Work with article safely
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Context Types and Concurrency
|
|
45
|
+
|
|
46
|
+
### View Context (Main Queue)
|
|
47
|
+
|
|
48
|
+
Runs on the main thread. Use for all UI operations.
|
|
49
|
+
|
|
50
|
+
```swift
|
|
51
|
+
let viewContext = container.viewContext
|
|
52
|
+
viewContext.perform {
|
|
53
|
+
// Runs on main thread
|
|
54
|
+
let article = Article(context: viewContext)
|
|
55
|
+
article.name = "New Article"
|
|
56
|
+
try? viewContext.save()
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Characteristics:**
|
|
61
|
+
- Main queue concurrency type
|
|
62
|
+
- Runs on main thread
|
|
63
|
+
- Use for UI-related operations only
|
|
64
|
+
- Keep operations lightweight
|
|
65
|
+
|
|
66
|
+
### Background Context (Private Queue)
|
|
67
|
+
|
|
68
|
+
Runs on a private queue. Use for heavy work.
|
|
69
|
+
|
|
70
|
+
```swift
|
|
71
|
+
let backgroundContext = container.newBackgroundContext()
|
|
72
|
+
backgroundContext.perform {
|
|
73
|
+
// Runs on private background queue
|
|
74
|
+
for i in 0..<1000 {
|
|
75
|
+
let article = Article(context: backgroundContext)
|
|
76
|
+
article.name = "Article \(i)"
|
|
77
|
+
}
|
|
78
|
+
try? backgroundContext.save()
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Characteristics:**
|
|
83
|
+
- Private queue concurrency type
|
|
84
|
+
- Runs on background thread
|
|
85
|
+
- Use for imports, exports, batch operations
|
|
86
|
+
- Doesn't block UI
|
|
87
|
+
|
|
88
|
+
## perform vs performAndWait
|
|
89
|
+
|
|
90
|
+
### perform (Asynchronous - Preferred)
|
|
91
|
+
|
|
92
|
+
```swift
|
|
93
|
+
context.perform {
|
|
94
|
+
// Work happens asynchronously
|
|
95
|
+
let article = Article(context: context)
|
|
96
|
+
try? context.save()
|
|
97
|
+
}
|
|
98
|
+
// Code here runs immediately, before perform block finishes
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Benefits:**
|
|
102
|
+
- Non-blocking
|
|
103
|
+
- Better performance
|
|
104
|
+
- Recommended for most cases
|
|
105
|
+
|
|
106
|
+
### performAndWait (Synchronous - Use Sparingly)
|
|
107
|
+
|
|
108
|
+
```swift
|
|
109
|
+
context.performAndWait {
|
|
110
|
+
// Work happens synchronously
|
|
111
|
+
let article = Article(context: context)
|
|
112
|
+
try? context.save()
|
|
113
|
+
}
|
|
114
|
+
// Code here runs after perform block finishes
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Caution:**
|
|
118
|
+
- Blocks the calling thread
|
|
119
|
+
- Can block main thread even for background contexts
|
|
120
|
+
- Use only when you need the result immediately
|
|
121
|
+
|
|
122
|
+
**Example of blocking behavior:**
|
|
123
|
+
|
|
124
|
+
```swift
|
|
125
|
+
// Called from main thread
|
|
126
|
+
let backgroundContext = container.newBackgroundContext()
|
|
127
|
+
|
|
128
|
+
// This BLOCKS the main thread!
|
|
129
|
+
backgroundContext.performAndWait {
|
|
130
|
+
// Heavy work here blocks UI
|
|
131
|
+
for i in 0..<10000 {
|
|
132
|
+
let article = Article(context: backgroundContext)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Common Threading Patterns
|
|
138
|
+
|
|
139
|
+
### Pattern 1: Background Import
|
|
140
|
+
|
|
141
|
+
```swift
|
|
142
|
+
func importArticles(_ data: [ArticleData]) {
|
|
143
|
+
let backgroundContext = container.newBackgroundContext()
|
|
144
|
+
backgroundContext.perform {
|
|
145
|
+
for item in data {
|
|
146
|
+
let article = Article(context: backgroundContext)
|
|
147
|
+
article.name = item.name
|
|
148
|
+
article.content = item.content
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
do {
|
|
152
|
+
try backgroundContext.save()
|
|
153
|
+
} catch {
|
|
154
|
+
print("Failed to save: \(error)")
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Pattern 2: Update Object from Background
|
|
161
|
+
|
|
162
|
+
```swift
|
|
163
|
+
func updateArticle(_ article: Article, newName: String) {
|
|
164
|
+
let objectID = article.objectID
|
|
165
|
+
let backgroundContext = container.newBackgroundContext()
|
|
166
|
+
|
|
167
|
+
backgroundContext.perform {
|
|
168
|
+
guard let article = try? backgroundContext.existingObject(with: objectID) as? Article else {
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
article.name = newName
|
|
173
|
+
try? backgroundContext.save()
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Pattern 3: Fetch in Background, Update UI on Main
|
|
179
|
+
|
|
180
|
+
```swift
|
|
181
|
+
func loadArticles(completion: @escaping ([Article]) -> Void) {
|
|
182
|
+
let backgroundContext = container.newBackgroundContext()
|
|
183
|
+
|
|
184
|
+
backgroundContext.perform {
|
|
185
|
+
let fetchRequest = Article.fetchRequest()
|
|
186
|
+
guard let articles = try? backgroundContext.fetch(fetchRequest) else {
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Get object IDs (thread-safe)
|
|
191
|
+
let objectIDs = articles.map { $0.objectID }
|
|
192
|
+
|
|
193
|
+
// Switch to main context for UI update
|
|
194
|
+
DispatchQueue.main.async {
|
|
195
|
+
let viewContext = self.container.viewContext
|
|
196
|
+
let mainArticles = objectIDs.compactMap {
|
|
197
|
+
try? viewContext.existingObject(with: $0) as? Article
|
|
198
|
+
}
|
|
199
|
+
completion(mainArticles)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Pattern 4: Batch Delete with Object IDs
|
|
206
|
+
|
|
207
|
+
```swift
|
|
208
|
+
func deleteArticles(_ articles: [Article]) {
|
|
209
|
+
let objectIDs = articles.map { $0.objectID }
|
|
210
|
+
let backgroundContext = container.newBackgroundContext()
|
|
211
|
+
|
|
212
|
+
backgroundContext.perform {
|
|
213
|
+
for objectID in objectIDs {
|
|
214
|
+
guard let article = try? backgroundContext.existingObject(with: objectID) else {
|
|
215
|
+
continue
|
|
216
|
+
}
|
|
217
|
+
backgroundContext.delete(article)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
try? backgroundContext.save()
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Context Hierarchy and Parent Contexts
|
|
226
|
+
|
|
227
|
+
### Child Context Pattern
|
|
228
|
+
|
|
229
|
+
```swift
|
|
230
|
+
// Parent context (view context)
|
|
231
|
+
let parentContext = container.viewContext
|
|
232
|
+
|
|
233
|
+
// Child context for editing
|
|
234
|
+
let childContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
|
|
235
|
+
childContext.parent = parentContext
|
|
236
|
+
|
|
237
|
+
// Make changes in child
|
|
238
|
+
let article = childContext.object(with: articleID) as! Article
|
|
239
|
+
article.name = "Updated"
|
|
240
|
+
|
|
241
|
+
// Save to parent (not to disk yet)
|
|
242
|
+
try? childContext.save()
|
|
243
|
+
|
|
244
|
+
// Save parent to persist
|
|
245
|
+
try? parentContext.save()
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Benefits:**
|
|
249
|
+
- Can discard changes by not saving child
|
|
250
|
+
- Useful for forms/editing
|
|
251
|
+
- Isolates changes
|
|
252
|
+
|
|
253
|
+
**Caution:**
|
|
254
|
+
- Adds complexity
|
|
255
|
+
- Two saves required for persistence
|
|
256
|
+
- Parent must be saved for changes to persist
|
|
257
|
+
|
|
258
|
+
## Debugging Threading Issues
|
|
259
|
+
|
|
260
|
+
### Enable Concurrency Debug
|
|
261
|
+
|
|
262
|
+
Add launch argument:
|
|
263
|
+
```
|
|
264
|
+
-com.apple.CoreData.ConcurrencyDebug 1
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**What it catches:**
|
|
268
|
+
- Objects accessed from wrong thread
|
|
269
|
+
- Contexts used from wrong queue
|
|
270
|
+
- Thread safety violations
|
|
271
|
+
|
|
272
|
+
**Example error:**
|
|
273
|
+
```
|
|
274
|
+
CoreData: error: Serious application error.
|
|
275
|
+
An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:.
|
|
276
|
+
*** -[NSManagedObjectContext performSelector:withObject:] called from thread which is not the context's thread with userInfo (null)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Common Threading Errors
|
|
280
|
+
|
|
281
|
+
#### Error 1: Accessing Object from Wrong Context
|
|
282
|
+
|
|
283
|
+
```swift
|
|
284
|
+
// ❌ Wrong
|
|
285
|
+
let article = viewContext.object(...)
|
|
286
|
+
backgroundContext.perform {
|
|
287
|
+
print(article.name) // CRASH!
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ✅ Correct
|
|
291
|
+
let objectID = article.objectID
|
|
292
|
+
backgroundContext.perform {
|
|
293
|
+
if let article = try? backgroundContext.existingObject(with: objectID) as? Article {
|
|
294
|
+
print(article.name)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
#### Error 2: Not Using perform
|
|
300
|
+
|
|
301
|
+
```swift
|
|
302
|
+
// ❌ Wrong
|
|
303
|
+
let backgroundContext = container.newBackgroundContext()
|
|
304
|
+
let article = Article(context: backgroundContext) // CRASH!
|
|
305
|
+
|
|
306
|
+
// ✅ Correct
|
|
307
|
+
let backgroundContext = container.newBackgroundContext()
|
|
308
|
+
backgroundContext.perform {
|
|
309
|
+
let article = Article(context: backgroundContext)
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
#### Error 3: Passing Context Between Threads
|
|
314
|
+
|
|
315
|
+
```swift
|
|
316
|
+
// ❌ Wrong
|
|
317
|
+
DispatchQueue.global().async {
|
|
318
|
+
try? viewContext.save() // CRASH!
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// ✅ Correct
|
|
322
|
+
viewContext.perform {
|
|
323
|
+
try? viewContext.save()
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## Merging Changes Between Contexts
|
|
328
|
+
|
|
329
|
+
### Automatic Merging
|
|
330
|
+
|
|
331
|
+
Enable automatic merging from parent:
|
|
332
|
+
|
|
333
|
+
```swift
|
|
334
|
+
context.automaticallyMergesChangesFromParent = true
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**Benefits:**
|
|
338
|
+
- Changes from other contexts automatically merge
|
|
339
|
+
- No manual merge code needed
|
|
340
|
+
- Recommended for most cases
|
|
341
|
+
|
|
342
|
+
### Manual Merging
|
|
343
|
+
|
|
344
|
+
Listen for save notifications:
|
|
345
|
+
|
|
346
|
+
```swift
|
|
347
|
+
NotificationCenter.default.addObserver(
|
|
348
|
+
self,
|
|
349
|
+
selector: #selector(contextDidSave),
|
|
350
|
+
name: .NSManagedObjectContextDidSave,
|
|
351
|
+
object: backgroundContext
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
@objc func contextDidSave(_ notification: Notification) {
|
|
355
|
+
viewContext.perform {
|
|
356
|
+
viewContext.mergeChanges(fromContextDidSave: notification)
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## Async/Await with Core Data (iOS 15+)
|
|
362
|
+
|
|
363
|
+
### Using async/await
|
|
364
|
+
|
|
365
|
+
```swift
|
|
366
|
+
func fetchArticles() async throws -> [Article] {
|
|
367
|
+
let context = container.newBackgroundContext()
|
|
368
|
+
|
|
369
|
+
return try await context.perform {
|
|
370
|
+
let fetchRequest = Article.fetchRequest()
|
|
371
|
+
return try context.fetch(fetchRequest)
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Usage
|
|
376
|
+
Task {
|
|
377
|
+
do {
|
|
378
|
+
let articles = try await fetchArticles()
|
|
379
|
+
// Update UI with articles
|
|
380
|
+
} catch {
|
|
381
|
+
print("Failed to fetch: \(error)")
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### Saving with async/await
|
|
387
|
+
|
|
388
|
+
```swift
|
|
389
|
+
func saveArticle(name: String) async throws {
|
|
390
|
+
let context = container.newBackgroundContext()
|
|
391
|
+
|
|
392
|
+
try await context.perform {
|
|
393
|
+
let article = Article(context: context)
|
|
394
|
+
article.name = name
|
|
395
|
+
try context.save()
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Performance Considerations
|
|
401
|
+
|
|
402
|
+
### Context Reuse
|
|
403
|
+
|
|
404
|
+
```swift
|
|
405
|
+
// ❌ Bad: Creating new context for each operation
|
|
406
|
+
func updateArticle1() {
|
|
407
|
+
let context = container.newBackgroundContext()
|
|
408
|
+
context.perform { /* ... */ }
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
func updateArticle2() {
|
|
412
|
+
let context = container.newBackgroundContext() // New context!
|
|
413
|
+
context.perform { /* ... */ }
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ✅ Better: Reuse context for related operations
|
|
417
|
+
class DataManager {
|
|
418
|
+
private lazy var backgroundContext = container.newBackgroundContext()
|
|
419
|
+
|
|
420
|
+
func updateArticle1() {
|
|
421
|
+
backgroundContext.perform { /* ... */ }
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
func updateArticle2() {
|
|
425
|
+
backgroundContext.perform { /* ... */ }
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Context Reset
|
|
431
|
+
|
|
432
|
+
For long-running contexts, periodically reset to free memory:
|
|
433
|
+
|
|
434
|
+
```swift
|
|
435
|
+
backgroundContext.perform {
|
|
436
|
+
for (index, data) in largeDataset.enumerated() {
|
|
437
|
+
let article = Article(context: backgroundContext)
|
|
438
|
+
article.name = data.name
|
|
439
|
+
|
|
440
|
+
if index % 100 == 0 {
|
|
441
|
+
try? backgroundContext.save()
|
|
442
|
+
backgroundContext.reset() // Clear memory
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
## Thread Confinement
|
|
449
|
+
|
|
450
|
+
Each context is confined to its queue. You can call `perform` from any thread, but all Core Data work must run inside `perform`/`performAndWait` on that context.
|
|
451
|
+
|
|
452
|
+
```swift
|
|
453
|
+
let context = container.newBackgroundContext()
|
|
454
|
+
|
|
455
|
+
// ✅ Allowed: scheduling work from anywhere
|
|
456
|
+
DispatchQueue.global().async {
|
|
457
|
+
context.perform {
|
|
458
|
+
// Work executes on context's queue
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
DispatchQueue.main.async {
|
|
463
|
+
context.perform {
|
|
464
|
+
// Also executes on context's queue
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ❌ Wrong: touching the context or its objects outside perform
|
|
469
|
+
DispatchQueue.global().async {
|
|
470
|
+
let article = Article(context: context) // Not inside perform
|
|
471
|
+
try? context.save() // Not inside perform
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
## Common Pitfalls
|
|
476
|
+
|
|
477
|
+
### ❌ Passing Objects Directly
|
|
478
|
+
|
|
479
|
+
```swift
|
|
480
|
+
func updateInBackground(_ article: Article) {
|
|
481
|
+
backgroundContext.perform {
|
|
482
|
+
article.name = "Updated" // CRASH!
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### ❌ Not Using perform
|
|
488
|
+
|
|
489
|
+
```swift
|
|
490
|
+
let backgroundContext = container.newBackgroundContext()
|
|
491
|
+
let article = Article(context: backgroundContext) // CRASH!
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
### ❌ Accessing UI from Background Context
|
|
495
|
+
|
|
496
|
+
```swift
|
|
497
|
+
backgroundContext.perform {
|
|
498
|
+
let articles = try? backgroundContext.fetch(Article.fetchRequest())
|
|
499
|
+
tableView.reloadData() // CRASH! Wrong thread
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### ❌ Using performAndWait on Main Thread
|
|
504
|
+
|
|
505
|
+
```swift
|
|
506
|
+
// On main thread
|
|
507
|
+
backgroundContext.performAndWait {
|
|
508
|
+
// Heavy work - blocks UI!
|
|
509
|
+
}
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### ✅ Correct Patterns
|
|
513
|
+
|
|
514
|
+
```swift
|
|
515
|
+
// Pass object IDs
|
|
516
|
+
func updateInBackground(_ article: Article) {
|
|
517
|
+
let objectID = article.objectID
|
|
518
|
+
backgroundContext.perform {
|
|
519
|
+
guard let article = try? backgroundContext.existingObject(with: objectID) as? Article else {
|
|
520
|
+
return
|
|
521
|
+
}
|
|
522
|
+
article.name = "Updated"
|
|
523
|
+
try? backgroundContext.save()
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Always use perform
|
|
528
|
+
let backgroundContext = container.newBackgroundContext()
|
|
529
|
+
backgroundContext.perform {
|
|
530
|
+
let article = Article(context: backgroundContext)
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Update UI on main thread
|
|
534
|
+
backgroundContext.perform {
|
|
535
|
+
let articles = try? backgroundContext.fetch(Article.fetchRequest())
|
|
536
|
+
let objectIDs = articles?.map { $0.objectID } ?? []
|
|
537
|
+
|
|
538
|
+
DispatchQueue.main.async {
|
|
539
|
+
// Update UI with objectIDs
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Use perform (async) instead of performAndWait
|
|
544
|
+
backgroundContext.perform {
|
|
545
|
+
// Heavy work doesn't block UI
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
## Testing Threading
|
|
550
|
+
|
|
551
|
+
```swift
|
|
552
|
+
func testThreadSafety() {
|
|
553
|
+
let expectation = XCTestExpectation(description: "Background save")
|
|
554
|
+
|
|
555
|
+
let objectID = article.objectID
|
|
556
|
+
let backgroundContext = container.newBackgroundContext()
|
|
557
|
+
|
|
558
|
+
backgroundContext.perform {
|
|
559
|
+
guard let article = try? backgroundContext.existingObject(with: objectID) as? Article else {
|
|
560
|
+
XCTFail("Failed to fetch article")
|
|
561
|
+
return
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
article.name = "Updated"
|
|
565
|
+
|
|
566
|
+
do {
|
|
567
|
+
try backgroundContext.save()
|
|
568
|
+
expectation.fulfill()
|
|
569
|
+
} catch {
|
|
570
|
+
XCTFail("Failed to save: \(error)")
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
wait(for: [expectation], timeout: 5.0)
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
## Summary
|
|
579
|
+
|
|
580
|
+
1. **Never pass NSManagedObject between contexts** - Always use NSManagedObjectID
|
|
581
|
+
2. **Always use `perform` or `performAndWait`** - Never access context directly
|
|
582
|
+
3. **Prefer `perform` over `performAndWait`** - Avoid blocking
|
|
583
|
+
4. **Use view context for UI only** - Heavy work in background contexts
|
|
584
|
+
5. **Enable `-com.apple.CoreData.ConcurrencyDebug 1`** - Catch threading violations
|
|
585
|
+
6. **Enable `automaticallyMergesChangesFromParent`** - Automatic change propagation
|
|
586
|
+
7. **Use async/await on iOS 15+** - Cleaner asynchronous code
|
|
587
|
+
8. **Reset contexts periodically** - Free memory in long-running operations
|
|
588
|
+
9. **One context per queue** - Don't share contexts across queues
|
|
589
|
+
10. **Test threading behavior** - Verify thread safety in tests
|