opencodekit 0.16.14 → 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/dcp.jsonc +10 -9
- 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,543 @@
|
|
|
1
|
+
# Batch Operations
|
|
2
|
+
|
|
3
|
+
Batch operations provide significant performance improvements for large-scale data modifications. They operate directly at the SQL level, bypassing the object graph.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Core Data provides three batch operation types:
|
|
8
|
+
- **NSBatchInsertRequest** - Bulk inserts (iOS 14+)
|
|
9
|
+
- **NSBatchDeleteRequest** - Bulk deletes
|
|
10
|
+
- **NSBatchUpdateRequest** - Bulk updates
|
|
11
|
+
|
|
12
|
+
**Key Characteristics:**
|
|
13
|
+
- Operate at SQL level (very fast)
|
|
14
|
+
- Don't load objects into memory
|
|
15
|
+
- Don't trigger validation
|
|
16
|
+
- Don't send change notifications (requires persistent history tracking)
|
|
17
|
+
- Can't set relationships during batch insert
|
|
18
|
+
|
|
19
|
+
## NSBatchInsertRequest (iOS 14+)
|
|
20
|
+
|
|
21
|
+
### Basic Usage
|
|
22
|
+
|
|
23
|
+
```swift
|
|
24
|
+
let context = container.newBackgroundContext()
|
|
25
|
+
|
|
26
|
+
context.perform {
|
|
27
|
+
let batchInsert = NSBatchInsertRequest(entity: Article.entity()) { (object: NSManagedObject) -> Bool in
|
|
28
|
+
guard let article = object as? Article else { return true }
|
|
29
|
+
|
|
30
|
+
article.name = "Sample Article"
|
|
31
|
+
article.content = "Content here"
|
|
32
|
+
article.creationDate = Date()
|
|
33
|
+
|
|
34
|
+
return false // Continue inserting
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
do {
|
|
38
|
+
try context.execute(batchInsert)
|
|
39
|
+
} catch {
|
|
40
|
+
print("Batch insert failed: \(error)")
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Inserting Multiple Objects
|
|
46
|
+
|
|
47
|
+
```swift
|
|
48
|
+
func batchInsertArticles(_ data: [ArticleData]) {
|
|
49
|
+
let context = container.newBackgroundContext()
|
|
50
|
+
|
|
51
|
+
context.perform {
|
|
52
|
+
var index = 0
|
|
53
|
+
let batchInsert = NSBatchInsertRequest(
|
|
54
|
+
entity: Article.entity()
|
|
55
|
+
) { (object: NSManagedObject) -> Bool in
|
|
56
|
+
guard index < data.count else { return true } // Stop
|
|
57
|
+
guard let article = object as? Article else { return true }
|
|
58
|
+
|
|
59
|
+
let articleData = data[index]
|
|
60
|
+
article.name = articleData.name
|
|
61
|
+
article.content = articleData.content
|
|
62
|
+
article.creationDate = Date()
|
|
63
|
+
|
|
64
|
+
index += 1
|
|
65
|
+
return false // Continue
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
do {
|
|
69
|
+
try context.execute(batchInsert)
|
|
70
|
+
} catch {
|
|
71
|
+
print("Batch insert failed: \(error)")
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Using Dictionary Representation (Alternative)
|
|
78
|
+
|
|
79
|
+
```swift
|
|
80
|
+
let context = container.newBackgroundContext()
|
|
81
|
+
|
|
82
|
+
context.perform {
|
|
83
|
+
let objects: [[String: Any]] = [
|
|
84
|
+
["name": "Article 1", "content": "Content 1", "creationDate": Date()],
|
|
85
|
+
["name": "Article 2", "content": "Content 2", "creationDate": Date()],
|
|
86
|
+
["name": "Article 3", "content": "Content 3", "creationDate": Date()]
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
let batchInsert = NSBatchInsertRequest(
|
|
90
|
+
entity: Article.entity(),
|
|
91
|
+
objects: objects
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
do {
|
|
95
|
+
try context.execute(batchInsert)
|
|
96
|
+
} catch {
|
|
97
|
+
print("Batch insert failed: \(error)")
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Limitations
|
|
103
|
+
|
|
104
|
+
**Cannot set relationships:**
|
|
105
|
+
```swift
|
|
106
|
+
// ❌ This won't work
|
|
107
|
+
let batchInsert = NSBatchInsertRequest(entity: Article.entity()) { object in
|
|
108
|
+
guard let article = object as? Article else { return true }
|
|
109
|
+
article.category = someCategory // Can't set relationships!
|
|
110
|
+
return false
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Workaround:** Set relationships after batch insert:
|
|
115
|
+
```swift
|
|
116
|
+
// 1. Batch insert articles
|
|
117
|
+
let batchInsert = NSBatchInsertRequest(entity: Article.entity()) { object in
|
|
118
|
+
guard let article = object as? Article else { return true }
|
|
119
|
+
article.name = "Article"
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
try context.execute(batchInsert)
|
|
123
|
+
|
|
124
|
+
// 2. Fetch and set relationships
|
|
125
|
+
let fetchRequest = Article.fetchRequest()
|
|
126
|
+
let articles = try context.fetch(fetchRequest)
|
|
127
|
+
for article in articles {
|
|
128
|
+
article.category = defaultCategory
|
|
129
|
+
}
|
|
130
|
+
try context.save()
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## NSBatchDeleteRequest
|
|
134
|
+
|
|
135
|
+
### Basic Usage
|
|
136
|
+
|
|
137
|
+
```swift
|
|
138
|
+
let context = container.newBackgroundContext()
|
|
139
|
+
|
|
140
|
+
context.perform {
|
|
141
|
+
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = Article.fetchRequest()
|
|
142
|
+
let batchDelete = NSBatchDeleteRequest(fetchRequest: fetchRequest)
|
|
143
|
+
|
|
144
|
+
do {
|
|
145
|
+
try context.execute(batchDelete)
|
|
146
|
+
} catch {
|
|
147
|
+
print("Batch delete failed: \(error)")
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### With Predicate
|
|
153
|
+
|
|
154
|
+
```swift
|
|
155
|
+
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = Article.fetchRequest()
|
|
156
|
+
fetchRequest.predicate = NSPredicate(format: "views < %d", 10)
|
|
157
|
+
|
|
158
|
+
let batchDelete = NSBatchDeleteRequest(fetchRequest: fetchRequest)
|
|
159
|
+
|
|
160
|
+
context.perform {
|
|
161
|
+
do {
|
|
162
|
+
try context.execute(batchDelete)
|
|
163
|
+
} catch {
|
|
164
|
+
print("Batch delete failed: \(error)")
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Getting Deleted Object IDs
|
|
170
|
+
|
|
171
|
+
```swift
|
|
172
|
+
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = Article.fetchRequest()
|
|
173
|
+
let batchDelete = NSBatchDeleteRequest(fetchRequest: fetchRequest)
|
|
174
|
+
batchDelete.resultType = .resultTypeObjectIDs
|
|
175
|
+
|
|
176
|
+
context.perform {
|
|
177
|
+
do {
|
|
178
|
+
let result = try context.execute(batchDelete) as? NSBatchDeleteResult
|
|
179
|
+
if let objectIDs = result?.result as? [NSManagedObjectID] {
|
|
180
|
+
print("Deleted \(objectIDs.count) objects")
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
print("Batch delete failed: \(error)")
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## NSBatchUpdateRequest
|
|
189
|
+
|
|
190
|
+
### Basic Usage
|
|
191
|
+
|
|
192
|
+
```swift
|
|
193
|
+
let context = container.newBackgroundContext()
|
|
194
|
+
|
|
195
|
+
context.perform {
|
|
196
|
+
let batchUpdate = NSBatchUpdateRequest(entityName: "Article")
|
|
197
|
+
batchUpdate.predicate = NSPredicate(format: "isRead == NO")
|
|
198
|
+
batchUpdate.propertiesToUpdate = ["isRead": true]
|
|
199
|
+
|
|
200
|
+
do {
|
|
201
|
+
try context.execute(batchUpdate)
|
|
202
|
+
} catch {
|
|
203
|
+
print("Batch update failed: \(error)")
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Updating Multiple Properties
|
|
209
|
+
|
|
210
|
+
```swift
|
|
211
|
+
let batchUpdate = NSBatchUpdateRequest(entityName: "Article")
|
|
212
|
+
batchUpdate.predicate = NSPredicate(format: "views < %d", 100)
|
|
213
|
+
batchUpdate.propertiesToUpdate = [
|
|
214
|
+
"views": 100,
|
|
215
|
+
"lastModified": Date(),
|
|
216
|
+
"isPopular": true
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
context.perform {
|
|
220
|
+
try? context.execute(batchUpdate)
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Using Expressions
|
|
225
|
+
|
|
226
|
+
```swift
|
|
227
|
+
// Increment views by 1
|
|
228
|
+
let batchUpdate = NSBatchUpdateRequest(entityName: "Article")
|
|
229
|
+
batchUpdate.propertiesToUpdate = [
|
|
230
|
+
"views": NSExpression(format: "views + 1")
|
|
231
|
+
]
|
|
232
|
+
|
|
233
|
+
context.perform {
|
|
234
|
+
try? context.execute(batchUpdate)
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Getting Updated Object IDs
|
|
239
|
+
|
|
240
|
+
```swift
|
|
241
|
+
let batchUpdate = NSBatchUpdateRequest(entityName: "Article")
|
|
242
|
+
batchUpdate.propertiesToUpdate = ["isRead": true]
|
|
243
|
+
batchUpdate.resultType = .updatedObjectIDsResultType
|
|
244
|
+
|
|
245
|
+
context.perform {
|
|
246
|
+
do {
|
|
247
|
+
let result = try context.execute(batchUpdate) as? NSBatchUpdateResult
|
|
248
|
+
if let objectIDs = result?.result as? [NSManagedObjectID] {
|
|
249
|
+
print("Updated \(objectIDs.count) objects")
|
|
250
|
+
}
|
|
251
|
+
} catch {
|
|
252
|
+
print("Batch update failed: \(error)")
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Persistent History Tracking Integration
|
|
258
|
+
|
|
259
|
+
**Critical:** Batch operations don't send change notifications. You **must** enable persistent history tracking for UI updates.
|
|
260
|
+
|
|
261
|
+
### Enable Persistent History Tracking
|
|
262
|
+
|
|
263
|
+
```swift
|
|
264
|
+
guard let description = container.persistentStoreDescriptions.first else { return }
|
|
265
|
+
|
|
266
|
+
description.setOption(true as NSNumber,
|
|
267
|
+
forKey: NSPersistentHistoryTrackingKey)
|
|
268
|
+
description.setOption(true as NSNumber,
|
|
269
|
+
forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Observe Remote Changes
|
|
273
|
+
|
|
274
|
+
```swift
|
|
275
|
+
NotificationCenter.default.addObserver(
|
|
276
|
+
self,
|
|
277
|
+
selector: #selector(storeRemoteChange),
|
|
278
|
+
name: .NSPersistentStoreRemoteChange,
|
|
279
|
+
object: container.persistentStoreCoordinator
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
@objc func storeRemoteChange(_ notification: Notification) {
|
|
283
|
+
// Merge changes into view context
|
|
284
|
+
// See persistent-history.md for full implementation
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## Performance Comparison
|
|
289
|
+
|
|
290
|
+
### Traditional Insert (Slow)
|
|
291
|
+
|
|
292
|
+
```swift
|
|
293
|
+
// Inserting 1000 objects: ~10 seconds
|
|
294
|
+
for i in 0..<1000 {
|
|
295
|
+
let article = Article(context: context)
|
|
296
|
+
article.name = "Article \(i)"
|
|
297
|
+
}
|
|
298
|
+
try context.save()
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Batch Insert (Fast)
|
|
302
|
+
|
|
303
|
+
```swift
|
|
304
|
+
// Inserting 1000 objects: ~0.5 seconds
|
|
305
|
+
var index = 0
|
|
306
|
+
let batchInsert = NSBatchInsertRequest(entity: Article.entity()) { object in
|
|
307
|
+
guard index < 1000 else { return true }
|
|
308
|
+
guard let article = object as? Article else { return true }
|
|
309
|
+
article.name = "Article \(index)"
|
|
310
|
+
index += 1
|
|
311
|
+
return false
|
|
312
|
+
}
|
|
313
|
+
try context.execute(batchInsert)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**Performance gain: ~20x faster**
|
|
317
|
+
|
|
318
|
+
## When to Use Batch Operations
|
|
319
|
+
|
|
320
|
+
### Use Batch Insert When:
|
|
321
|
+
- Importing large datasets (>100 objects)
|
|
322
|
+
- Initial data seeding
|
|
323
|
+
- Syncing data from server
|
|
324
|
+
- Performance is critical
|
|
325
|
+
|
|
326
|
+
### Use Batch Delete When:
|
|
327
|
+
- Deleting many objects at once
|
|
328
|
+
- Clearing old data
|
|
329
|
+
- Implementing data retention policies
|
|
330
|
+
- Performance is critical
|
|
331
|
+
|
|
332
|
+
### Use Batch Update When:
|
|
333
|
+
- Updating many objects with same values
|
|
334
|
+
- Bulk status changes
|
|
335
|
+
- Incrementing counters
|
|
336
|
+
- Performance is critical
|
|
337
|
+
|
|
338
|
+
### Don't Use Batch Operations When:
|
|
339
|
+
- Need to set relationships
|
|
340
|
+
- Need validation
|
|
341
|
+
- Need to trigger lifecycle events (willSave, etc.)
|
|
342
|
+
- Working with small datasets (<50 objects)
|
|
343
|
+
- Need immediate UI updates without persistent history tracking
|
|
344
|
+
|
|
345
|
+
## Complete Example: Import with Batch Insert
|
|
346
|
+
|
|
347
|
+
```swift
|
|
348
|
+
class DataImporter {
|
|
349
|
+
let container: NSPersistentContainer
|
|
350
|
+
|
|
351
|
+
init(container: NSPersistentContainer) {
|
|
352
|
+
self.container = container
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
func importArticles(_ data: [ArticleData]) {
|
|
356
|
+
let context = container.newBackgroundContext()
|
|
357
|
+
|
|
358
|
+
context.perform {
|
|
359
|
+
var index = 0
|
|
360
|
+
let batchInsert = NSBatchInsertRequest(
|
|
361
|
+
entity: Article.entity()
|
|
362
|
+
) { (object: NSManagedObject) -> Bool in
|
|
363
|
+
guard index < data.count else { return true }
|
|
364
|
+
guard let article = object as? Article else { return true }
|
|
365
|
+
|
|
366
|
+
let articleData = data[index]
|
|
367
|
+
article.name = articleData.name
|
|
368
|
+
article.content = articleData.content
|
|
369
|
+
article.views = 0
|
|
370
|
+
article.creationDate = Date()
|
|
371
|
+
|
|
372
|
+
index += 1
|
|
373
|
+
return false
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
do {
|
|
377
|
+
let result = try context.execute(batchInsert) as? NSBatchInsertResult
|
|
378
|
+
print("Inserted \(data.count) articles")
|
|
379
|
+
|
|
380
|
+
// If you need the object IDs
|
|
381
|
+
if let objectIDs = result?.result as? [NSManagedObjectID] {
|
|
382
|
+
print("Object IDs: \(objectIDs)")
|
|
383
|
+
}
|
|
384
|
+
} catch {
|
|
385
|
+
print("Batch insert failed: \(error)")
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Complete Example: Cleanup with Batch Delete
|
|
393
|
+
|
|
394
|
+
```swift
|
|
395
|
+
class DataCleaner {
|
|
396
|
+
let container: NSPersistentContainer
|
|
397
|
+
|
|
398
|
+
init(container: NSPersistentContainer) {
|
|
399
|
+
self.container = container
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
func deleteOldArticles(olderThan days: Int) {
|
|
403
|
+
let context = container.newBackgroundContext()
|
|
404
|
+
|
|
405
|
+
context.perform {
|
|
406
|
+
let cutoffDate = Calendar.current.date(
|
|
407
|
+
byAdding: .day,
|
|
408
|
+
value: -days,
|
|
409
|
+
to: Date()
|
|
410
|
+
)!
|
|
411
|
+
|
|
412
|
+
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = Article.fetchRequest()
|
|
413
|
+
fetchRequest.predicate = NSPredicate(
|
|
414
|
+
format: "creationDate < %@",
|
|
415
|
+
cutoffDate as NSDate
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
let batchDelete = NSBatchDeleteRequest(fetchRequest: fetchRequest)
|
|
419
|
+
batchDelete.resultType = .resultTypeCount
|
|
420
|
+
|
|
421
|
+
do {
|
|
422
|
+
let result = try context.execute(batchDelete) as? NSBatchDeleteResult
|
|
423
|
+
if let count = result?.result as? Int {
|
|
424
|
+
print("Deleted \(count) old articles")
|
|
425
|
+
}
|
|
426
|
+
} catch {
|
|
427
|
+
print("Batch delete failed: \(error)")
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## Common Pitfalls
|
|
435
|
+
|
|
436
|
+
### ❌ Not Enabling Persistent History Tracking
|
|
437
|
+
|
|
438
|
+
```swift
|
|
439
|
+
// Batch insert happens
|
|
440
|
+
let batchInsert = NSBatchInsertRequest(entity: Article.entity()) { ... }
|
|
441
|
+
try context.execute(batchInsert)
|
|
442
|
+
|
|
443
|
+
// UI doesn't update! No notifications sent
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### ❌ Trying to Set Relationships
|
|
447
|
+
|
|
448
|
+
```swift
|
|
449
|
+
let batchInsert = NSBatchInsertRequest(entity: Article.entity()) { object in
|
|
450
|
+
guard let article = object as? Article else { return true }
|
|
451
|
+
article.category = category // Won't work!
|
|
452
|
+
return false
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### ❌ Expecting Validation
|
|
457
|
+
|
|
458
|
+
```swift
|
|
459
|
+
// No validation happens!
|
|
460
|
+
let batchInsert = NSBatchInsertRequest(entity: Article.entity()) { object in
|
|
461
|
+
guard let article = object as? Article else { return true }
|
|
462
|
+
article.name = "" // Empty name - no validation error
|
|
463
|
+
return false
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### ❌ Using on View Context
|
|
468
|
+
|
|
469
|
+
```swift
|
|
470
|
+
// Don't use batch operations on view context
|
|
471
|
+
viewContext.perform {
|
|
472
|
+
let batchInsert = NSBatchInsertRequest(entity: Article.entity()) { ... }
|
|
473
|
+
try? viewContext.execute(batchInsert) // Blocks UI!
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### ✅ Correct Approach
|
|
478
|
+
|
|
479
|
+
```swift
|
|
480
|
+
// 1. Enable persistent history tracking
|
|
481
|
+
description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
|
|
482
|
+
|
|
483
|
+
// 2. Use background context
|
|
484
|
+
let context = container.newBackgroundContext()
|
|
485
|
+
|
|
486
|
+
// 3. Execute batch operation
|
|
487
|
+
context.perform {
|
|
488
|
+
let batchInsert = NSBatchInsertRequest(entity: Article.entity()) { object in
|
|
489
|
+
guard let article = object as? Article else { return true }
|
|
490
|
+
article.name = "Valid Name"
|
|
491
|
+
return false
|
|
492
|
+
}
|
|
493
|
+
try? context.execute(batchInsert)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// 4. UI updates via persistent history tracking
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
## Testing Batch Operations
|
|
500
|
+
|
|
501
|
+
```swift
|
|
502
|
+
func testBatchInsert() throws {
|
|
503
|
+
let context = container.newBackgroundContext()
|
|
504
|
+
|
|
505
|
+
let expectation = XCTestExpectation(description: "Batch insert")
|
|
506
|
+
|
|
507
|
+
context.perform {
|
|
508
|
+
var count = 0
|
|
509
|
+
let batchInsert = NSBatchInsertRequest(entity: Article.entity()) { object in
|
|
510
|
+
guard count < 10 else { return true }
|
|
511
|
+
guard let article = object as? Article else { return true }
|
|
512
|
+
article.name = "Article \(count)"
|
|
513
|
+
count += 1
|
|
514
|
+
return false
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
do {
|
|
518
|
+
try context.execute(batchInsert)
|
|
519
|
+
expectation.fulfill()
|
|
520
|
+
} catch {
|
|
521
|
+
XCTFail("Batch insert failed: \(error)")
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
wait(for: [expectation], timeout: 5.0)
|
|
526
|
+
|
|
527
|
+
// Verify
|
|
528
|
+
let fetchRequest = Article.fetchRequest()
|
|
529
|
+
let articles = try context.fetch(fetchRequest)
|
|
530
|
+
XCTAssertEqual(articles.count, 10)
|
|
531
|
+
}
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
## Summary
|
|
535
|
+
|
|
536
|
+
1. **Use batch operations for large datasets** - 10-20x performance improvement
|
|
537
|
+
2. **Enable persistent history tracking** - Required for UI updates
|
|
538
|
+
3. **Use background contexts** - Don't block UI
|
|
539
|
+
4. **Can't set relationships in batch insert** - Set them separately if needed
|
|
540
|
+
5. **No validation or lifecycle events** - Batch operations bypass object graph
|
|
541
|
+
6. **Get result types** - Use resultType to get object IDs or counts
|
|
542
|
+
7. **Test thoroughly** - Verify data integrity after batch operations
|
|
543
|
+
8. **Consider trade-offs** - Speed vs validation/relationships/lifecycle events
|