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,643 @@
|
|
|
1
|
+
# Fetch Requests and Querying
|
|
2
|
+
|
|
3
|
+
Optimizing fetch requests is crucial for app performance. This guide covers best practices for querying Core Data efficiently, from basic fetches to advanced aggregations.
|
|
4
|
+
|
|
5
|
+
## Basic Fetch Request
|
|
6
|
+
|
|
7
|
+
```swift
|
|
8
|
+
let fetchRequest: NSFetchRequest<Article> = Article.fetchRequest()
|
|
9
|
+
let articles = try context.fetch(fetchRequest)
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Optimization Strategies
|
|
13
|
+
|
|
14
|
+
### 1. Limit Properties Fetched
|
|
15
|
+
|
|
16
|
+
Only fetch the properties you actually need:
|
|
17
|
+
|
|
18
|
+
```swift
|
|
19
|
+
let fetchRequest = Article.fetchRequest()
|
|
20
|
+
fetchRequest.propertiesToFetch = ["name", "creationDate"]
|
|
21
|
+
|
|
22
|
+
// For list view, you might only need:
|
|
23
|
+
fetchRequest.propertiesToFetch = ["name", "categoryName", "views"]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**SQL Impact:**
|
|
27
|
+
```sql
|
|
28
|
+
-- Without propertiesToFetch
|
|
29
|
+
SELECT * FROM ZARTICLE
|
|
30
|
+
|
|
31
|
+
-- With propertiesToFetch
|
|
32
|
+
SELECT Z_PK, ZNAME, ZCREATIONDATE FROM ZARTICLE
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Benefits:**
|
|
36
|
+
- Reduces memory usage
|
|
37
|
+
- Faster query execution
|
|
38
|
+
- Less data transferred from disk
|
|
39
|
+
|
|
40
|
+
### 2. Use Batch Fetching
|
|
41
|
+
|
|
42
|
+
Fetch objects in batches to avoid loading everything at once:
|
|
43
|
+
|
|
44
|
+
```swift
|
|
45
|
+
let fetchRequest = Article.fetchRequest()
|
|
46
|
+
fetchRequest.fetchBatchSize = 20
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**How it works:**
|
|
50
|
+
- Initially fetches only 20 objects
|
|
51
|
+
- Fetches next batch when needed (scrolling, iteration)
|
|
52
|
+
- Keeps memory usage predictable
|
|
53
|
+
|
|
54
|
+
**When to use:**
|
|
55
|
+
- List views (table/collection views)
|
|
56
|
+
- Large datasets
|
|
57
|
+
- Scrollable content
|
|
58
|
+
|
|
59
|
+
### 3. Set Fetch Limit
|
|
60
|
+
|
|
61
|
+
When you only need a specific number of results:
|
|
62
|
+
|
|
63
|
+
```swift
|
|
64
|
+
let fetchRequest = Article.fetchRequest()
|
|
65
|
+
fetchRequest.fetchLimit = 1 // Only fetch one result
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Common use cases:**
|
|
69
|
+
```swift
|
|
70
|
+
// Get newest article
|
|
71
|
+
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
|
|
72
|
+
fetchRequest.fetchLimit = 1
|
|
73
|
+
|
|
74
|
+
// Get top 10 most viewed
|
|
75
|
+
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "views", ascending: false)]
|
|
76
|
+
fetchRequest.fetchLimit = 10
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 4. Fetch Only Object IDs
|
|
80
|
+
|
|
81
|
+
For counting or checking existence, fetch only IDs:
|
|
82
|
+
|
|
83
|
+
```swift
|
|
84
|
+
let fetchRequest = Article.fetchRequest()
|
|
85
|
+
fetchRequest.resultType = .managedObjectIDResultType
|
|
86
|
+
|
|
87
|
+
let objectIDs = try context.fetch(fetchRequest) as! [NSManagedObjectID]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Benefits:**
|
|
91
|
+
- Minimal memory usage
|
|
92
|
+
- Very fast
|
|
93
|
+
- No faulting overhead
|
|
94
|
+
|
|
95
|
+
**Use for:**
|
|
96
|
+
- Counting objects
|
|
97
|
+
- Checking existence
|
|
98
|
+
- Batch operations
|
|
99
|
+
- Validation
|
|
100
|
+
|
|
101
|
+
## Sort Descriptors
|
|
102
|
+
|
|
103
|
+
Always specify sort descriptors for predictable results:
|
|
104
|
+
|
|
105
|
+
```swift
|
|
106
|
+
let fetchRequest = Article.fetchRequest()
|
|
107
|
+
fetchRequest.sortDescriptors = [
|
|
108
|
+
NSSortDescriptor(key: "creationDate", ascending: false)
|
|
109
|
+
]
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Multiple Sort Descriptors
|
|
113
|
+
|
|
114
|
+
```swift
|
|
115
|
+
fetchRequest.sortDescriptors = [
|
|
116
|
+
NSSortDescriptor(key: "category.name", ascending: true),
|
|
117
|
+
NSSortDescriptor(key: "name", ascending: true)
|
|
118
|
+
]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Case-Insensitive Sorting
|
|
122
|
+
|
|
123
|
+
```swift
|
|
124
|
+
let sortDescriptor = NSSortDescriptor(
|
|
125
|
+
key: "name",
|
|
126
|
+
ascending: true,
|
|
127
|
+
selector: #selector(NSString.caseInsensitiveCompare(_:))
|
|
128
|
+
)
|
|
129
|
+
fetchRequest.sortDescriptors = [sortDescriptor]
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Localized Sorting
|
|
133
|
+
|
|
134
|
+
```swift
|
|
135
|
+
let sortDescriptor = NSSortDescriptor(
|
|
136
|
+
key: "name",
|
|
137
|
+
ascending: true,
|
|
138
|
+
selector: #selector(NSString.localizedStandardCompare(_:))
|
|
139
|
+
)
|
|
140
|
+
fetchRequest.sortDescriptors = [sortDescriptor]
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Predicates
|
|
144
|
+
|
|
145
|
+
Filter results using predicates:
|
|
146
|
+
|
|
147
|
+
### Basic Predicates
|
|
148
|
+
|
|
149
|
+
```swift
|
|
150
|
+
// Exact match
|
|
151
|
+
fetchRequest.predicate = NSPredicate(format: "name == %@", "SwiftLee")
|
|
152
|
+
|
|
153
|
+
// Contains
|
|
154
|
+
fetchRequest.predicate = NSPredicate(format: "name CONTAINS[cd] %@", "swift")
|
|
155
|
+
// [c] = case insensitive, [d] = diacritic insensitive
|
|
156
|
+
|
|
157
|
+
// Begins with
|
|
158
|
+
fetchRequest.predicate = NSPredicate(format: "name BEGINSWITH[c] %@", "Swift")
|
|
159
|
+
|
|
160
|
+
// Greater than
|
|
161
|
+
fetchRequest.predicate = NSPredicate(format: "views > %d", 100)
|
|
162
|
+
|
|
163
|
+
// Date range
|
|
164
|
+
let startDate = Calendar.current.startOfDay(for: Date())
|
|
165
|
+
let endDate = Calendar.current.date(byAdding: .day, value: 1, to: startDate)!
|
|
166
|
+
fetchRequest.predicate = NSPredicate(
|
|
167
|
+
format: "creationDate >= %@ AND creationDate < %@",
|
|
168
|
+
startDate as NSDate,
|
|
169
|
+
endDate as NSDate
|
|
170
|
+
)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Compound Predicates
|
|
174
|
+
|
|
175
|
+
```swift
|
|
176
|
+
// AND
|
|
177
|
+
let predicate1 = NSPredicate(format: "views > %d", 100)
|
|
178
|
+
let predicate2 = NSPredicate(format: "category.name == %@", "Swift")
|
|
179
|
+
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate1, predicate2])
|
|
180
|
+
|
|
181
|
+
// OR
|
|
182
|
+
fetchRequest.predicate = NSCompoundPredicate(orPredicateWithSubpredicates: [predicate1, predicate2])
|
|
183
|
+
|
|
184
|
+
// NOT
|
|
185
|
+
fetchRequest.predicate = NSCompoundPredicate(notPredicateWithSubpredicate: predicate1)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Relationship Predicates
|
|
189
|
+
|
|
190
|
+
```swift
|
|
191
|
+
// Articles with a specific category
|
|
192
|
+
fetchRequest.predicate = NSPredicate(format: "category.name == %@", "Swift")
|
|
193
|
+
|
|
194
|
+
// Articles with any attachments
|
|
195
|
+
fetchRequest.predicate = NSPredicate(format: "attachments.@count > 0")
|
|
196
|
+
|
|
197
|
+
// Articles with more than 5 attachments
|
|
198
|
+
fetchRequest.predicate = NSPredicate(format: "attachments.@count > 5")
|
|
199
|
+
|
|
200
|
+
// Using ANY
|
|
201
|
+
fetchRequest.predicate = NSPredicate(format: "ANY attachments.size > %d", 1000000)
|
|
202
|
+
|
|
203
|
+
// Using ALL
|
|
204
|
+
fetchRequest.predicate = NSPredicate(format: "ALL attachments.isDownloaded == YES")
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### IN Predicate
|
|
208
|
+
|
|
209
|
+
```swift
|
|
210
|
+
let names = ["Swift", "iOS", "Core Data"]
|
|
211
|
+
fetchRequest.predicate = NSPredicate(format: "name IN %@", names)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## NSFetchedResultsController
|
|
215
|
+
|
|
216
|
+
For table and collection views, use `NSFetchedResultsController` for automatic updates:
|
|
217
|
+
|
|
218
|
+
```swift
|
|
219
|
+
class ArticlesViewController: UIViewController {
|
|
220
|
+
var fetchedResultsController: NSFetchedResultsController<Article>!
|
|
221
|
+
|
|
222
|
+
func setupFetchedResultsController() {
|
|
223
|
+
let fetchRequest = Article.fetchRequest()
|
|
224
|
+
fetchRequest.sortDescriptors = [
|
|
225
|
+
NSSortDescriptor(key: "creationDate", ascending: false)
|
|
226
|
+
]
|
|
227
|
+
fetchRequest.fetchBatchSize = 20
|
|
228
|
+
|
|
229
|
+
fetchedResultsController = NSFetchedResultsController(
|
|
230
|
+
fetchRequest: fetchRequest,
|
|
231
|
+
managedObjectContext: viewContext,
|
|
232
|
+
sectionNameKeyPath: nil,
|
|
233
|
+
cacheName: "ArticlesCache"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
fetchedResultsController.delegate = self
|
|
237
|
+
|
|
238
|
+
try? fetchedResultsController.performFetch()
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### With Sections
|
|
244
|
+
|
|
245
|
+
```swift
|
|
246
|
+
fetchedResultsController = NSFetchedResultsController(
|
|
247
|
+
fetchRequest: fetchRequest,
|
|
248
|
+
managedObjectContext: viewContext,
|
|
249
|
+
sectionNameKeyPath: "category.name", // Group by category
|
|
250
|
+
cacheName: "ArticlesByCategoryCache"
|
|
251
|
+
)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Delegate Methods (UITableView)
|
|
255
|
+
|
|
256
|
+
```swift
|
|
257
|
+
extension ArticlesViewController: NSFetchedResultsControllerDelegate {
|
|
258
|
+
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
|
259
|
+
tableView.beginUpdates()
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,
|
|
263
|
+
didChange anObject: Any,
|
|
264
|
+
at indexPath: IndexPath?,
|
|
265
|
+
for type: NSFetchedResultsChangeType,
|
|
266
|
+
newIndexPath: IndexPath?) {
|
|
267
|
+
switch type {
|
|
268
|
+
case .insert:
|
|
269
|
+
if let indexPath = newIndexPath {
|
|
270
|
+
tableView.insertRows(at: [indexPath], with: .automatic)
|
|
271
|
+
}
|
|
272
|
+
case .delete:
|
|
273
|
+
if let indexPath = indexPath {
|
|
274
|
+
tableView.deleteRows(at: [indexPath], with: .automatic)
|
|
275
|
+
}
|
|
276
|
+
case .update:
|
|
277
|
+
if let indexPath = indexPath {
|
|
278
|
+
tableView.reloadRows(at: [indexPath], with: .automatic)
|
|
279
|
+
}
|
|
280
|
+
case .move:
|
|
281
|
+
if let indexPath = indexPath, let newIndexPath = newIndexPath {
|
|
282
|
+
tableView.deleteRows(at: [indexPath], with: .automatic)
|
|
283
|
+
tableView.insertRows(at: [newIndexPath], with: .automatic)
|
|
284
|
+
}
|
|
285
|
+
@unknown default:
|
|
286
|
+
break
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
|
|
291
|
+
tableView.endUpdates()
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## Diffable Data Sources (iOS 13+)
|
|
297
|
+
|
|
298
|
+
Modern approach using `NSDiffableDataSourceSnapshot`:
|
|
299
|
+
|
|
300
|
+
```swift
|
|
301
|
+
class ArticlesViewController: UICollectionViewController {
|
|
302
|
+
private var dataSource: UICollectionViewDiffableDataSource<String, NSManagedObjectID>!
|
|
303
|
+
private var fetchedResultsController: NSFetchedResultsController<Article>!
|
|
304
|
+
|
|
305
|
+
func setupDataSource() {
|
|
306
|
+
dataSource = UICollectionViewDiffableDataSource<String, NSManagedObjectID>(
|
|
307
|
+
collectionView: collectionView
|
|
308
|
+
) { collectionView, indexPath, objectID in
|
|
309
|
+
let cell = collectionView.dequeueReusableCell(
|
|
310
|
+
withReuseIdentifier: "ArticleCell",
|
|
311
|
+
for: indexPath
|
|
312
|
+
) as! ArticleCell
|
|
313
|
+
|
|
314
|
+
if let article = try? self.viewContext.existingObject(with: objectID) as? Article {
|
|
315
|
+
cell.configure(with: article)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return cell
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
func setupFetchedResultsController() {
|
|
323
|
+
let fetchRequest = Article.fetchRequest()
|
|
324
|
+
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)]
|
|
325
|
+
|
|
326
|
+
fetchedResultsController = NSFetchedResultsController(
|
|
327
|
+
fetchRequest: fetchRequest,
|
|
328
|
+
managedObjectContext: viewContext,
|
|
329
|
+
sectionNameKeyPath: nil,
|
|
330
|
+
cacheName: nil
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
fetchedResultsController.delegate = self
|
|
334
|
+
try? fetchedResultsController.performFetch()
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
extension ArticlesViewController: NSFetchedResultsControllerDelegate {
|
|
339
|
+
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,
|
|
340
|
+
didChangeContentWith snapshot: NSDiffableDataSourceSnapshotReference) {
|
|
341
|
+
let snapshot = snapshot as NSDiffableDataSourceSnapshot<String, NSManagedObjectID>
|
|
342
|
+
dataSource.apply(snapshot, animatingDifferences: true)
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Aggregate Fetching with NSExpression
|
|
348
|
+
|
|
349
|
+
For statistics and aggregations:
|
|
350
|
+
|
|
351
|
+
### Count
|
|
352
|
+
|
|
353
|
+
```swift
|
|
354
|
+
// Simple count
|
|
355
|
+
let count = try context.count(for: Article.fetchRequest())
|
|
356
|
+
|
|
357
|
+
// Count with predicate
|
|
358
|
+
let fetchRequest = Article.fetchRequest()
|
|
359
|
+
fetchRequest.predicate = NSPredicate(format: "views > %d", 100)
|
|
360
|
+
let count = try context.count(for: fetchRequest)
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Sum, Average, Min, Max
|
|
364
|
+
|
|
365
|
+
```swift
|
|
366
|
+
let fetchRequest = Article.fetchRequest()
|
|
367
|
+
fetchRequest.resultType = .dictionaryResultType
|
|
368
|
+
|
|
369
|
+
// Sum of views
|
|
370
|
+
let sumExpression = NSExpression(format: "@sum.views")
|
|
371
|
+
let sumDescription = NSExpressionDescription()
|
|
372
|
+
sumDescription.name = "totalViews"
|
|
373
|
+
sumDescription.expression = sumExpression
|
|
374
|
+
sumDescription.expressionResultType = .integer64AttributeType
|
|
375
|
+
|
|
376
|
+
fetchRequest.propertiesToFetch = [sumDescription]
|
|
377
|
+
|
|
378
|
+
let results = try context.fetch(fetchRequest) as! [[String: Any]]
|
|
379
|
+
if let totalViews = results.first?["totalViews"] as? Int {
|
|
380
|
+
print("Total views: \(totalViews)")
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Group By with Aggregates
|
|
385
|
+
|
|
386
|
+
```swift
|
|
387
|
+
let fetchRequest = Article.fetchRequest()
|
|
388
|
+
fetchRequest.resultType = .dictionaryResultType
|
|
389
|
+
|
|
390
|
+
// Category name
|
|
391
|
+
let categoryExpression = NSExpression(forKeyPath: "category.name")
|
|
392
|
+
let categoryDescription = NSExpressionDescription()
|
|
393
|
+
categoryDescription.name = "categoryName"
|
|
394
|
+
categoryDescription.expression = categoryExpression
|
|
395
|
+
categoryDescription.expressionResultType = .stringAttributeType
|
|
396
|
+
|
|
397
|
+
// Sum of views per category
|
|
398
|
+
let sumExpression = NSExpression(format: "@sum.views")
|
|
399
|
+
let sumDescription = NSExpressionDescription()
|
|
400
|
+
sumDescription.name = "totalViews"
|
|
401
|
+
sumDescription.expression = sumExpression
|
|
402
|
+
sumDescription.expressionResultType = .integer64AttributeType
|
|
403
|
+
|
|
404
|
+
fetchRequest.propertiesToFetch = [categoryDescription, sumDescription]
|
|
405
|
+
fetchRequest.propertiesToGroupBy = ["category.name"]
|
|
406
|
+
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "categoryName", ascending: true)]
|
|
407
|
+
|
|
408
|
+
let results = try context.fetch(fetchRequest) as! [[String: Any]]
|
|
409
|
+
for result in results {
|
|
410
|
+
let category = result["categoryName"] as? String ?? "Unknown"
|
|
411
|
+
let views = result["totalViews"] as? Int ?? 0
|
|
412
|
+
print("\(category): \(views) views")
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Count Per Group
|
|
417
|
+
|
|
418
|
+
```swift
|
|
419
|
+
let fetchRequest = Article.fetchRequest()
|
|
420
|
+
fetchRequest.resultType = .dictionaryResultType
|
|
421
|
+
|
|
422
|
+
let categoryExpression = NSExpression(forKeyPath: "category.name")
|
|
423
|
+
let categoryDescription = NSExpressionDescription()
|
|
424
|
+
categoryDescription.name = "categoryName"
|
|
425
|
+
categoryDescription.expression = categoryExpression
|
|
426
|
+
categoryDescription.expressionResultType = .stringAttributeType
|
|
427
|
+
|
|
428
|
+
let countExpression = NSExpression(forFunction: "count:", arguments: [NSExpression(forKeyPath: "objectID")])
|
|
429
|
+
let countDescription = NSExpressionDescription()
|
|
430
|
+
countDescription.name = "count"
|
|
431
|
+
countDescription.expression = countExpression
|
|
432
|
+
countDescription.expressionResultType = .integer64AttributeType
|
|
433
|
+
|
|
434
|
+
fetchRequest.propertiesToFetch = [categoryDescription, countDescription]
|
|
435
|
+
fetchRequest.propertiesToGroupBy = ["category.name"]
|
|
436
|
+
|
|
437
|
+
let results = try context.fetch(fetchRequest) as! [[String: Any]]
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
## Typed Fetch Requests with Managed Protocol
|
|
441
|
+
|
|
442
|
+
Create a protocol for type-safe fetch requests:
|
|
443
|
+
|
|
444
|
+
```swift
|
|
445
|
+
protocol Managed: NSManagedObject {
|
|
446
|
+
static var entityName: String { get }
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
extension Managed {
|
|
450
|
+
static var entityName: String {
|
|
451
|
+
return String(describing: self)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
static func fetchRequest<T: NSManagedObject>() -> NSFetchRequest<T> {
|
|
455
|
+
return NSFetchRequest<T>(entityName: entityName)
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Conform your entities
|
|
460
|
+
extension Article: Managed {}
|
|
461
|
+
|
|
462
|
+
// Usage
|
|
463
|
+
let fetchRequest: NSFetchRequest<Article> = Article.fetchRequest()
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
## Asynchronous Fetching
|
|
467
|
+
|
|
468
|
+
For large datasets, fetch asynchronously:
|
|
469
|
+
|
|
470
|
+
```swift
|
|
471
|
+
let fetchRequest = Article.fetchRequest()
|
|
472
|
+
let asyncFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { result in
|
|
473
|
+
guard let articles = result.finalResult else { return }
|
|
474
|
+
|
|
475
|
+
DispatchQueue.main.async {
|
|
476
|
+
// Update UI with articles
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
try? context.execute(asyncFetchRequest)
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## Faulting Control
|
|
484
|
+
|
|
485
|
+
### Prefetching Relationships
|
|
486
|
+
|
|
487
|
+
```swift
|
|
488
|
+
let fetchRequest = Article.fetchRequest()
|
|
489
|
+
fetchRequest.relationshipKeyPathsForPrefetching = ["category", "attachments"]
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
**Benefits:**
|
|
493
|
+
- Reduces number of database trips
|
|
494
|
+
- Improves performance when accessing relationships
|
|
495
|
+
- Prevents N+1 query problem
|
|
496
|
+
|
|
497
|
+
### Returning Faults
|
|
498
|
+
|
|
499
|
+
```swift
|
|
500
|
+
fetchRequest.returnsObjectsAsFaults = false
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
**When to use:**
|
|
504
|
+
- You know you'll access all properties immediately
|
|
505
|
+
- Small result sets
|
|
506
|
+
- Avoid for large datasets (high memory usage)
|
|
507
|
+
|
|
508
|
+
## Common Patterns
|
|
509
|
+
|
|
510
|
+
### Fetch Single Object by ID
|
|
511
|
+
|
|
512
|
+
```swift
|
|
513
|
+
func fetchArticle(withID id: NSManagedObjectID) -> Article? {
|
|
514
|
+
return try? context.existingObject(with: id) as? Article
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### Fetch or Create
|
|
519
|
+
|
|
520
|
+
```swift
|
|
521
|
+
func fetchOrCreateArticle(withName name: String) -> Article {
|
|
522
|
+
let fetchRequest = Article.fetchRequest()
|
|
523
|
+
fetchRequest.predicate = NSPredicate(format: "name == %@", name)
|
|
524
|
+
fetchRequest.fetchLimit = 1
|
|
525
|
+
|
|
526
|
+
if let existing = try? context.fetch(fetchRequest).first {
|
|
527
|
+
return existing
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
let article = Article(context: context)
|
|
531
|
+
article.name = name
|
|
532
|
+
return article
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### Check Existence
|
|
537
|
+
|
|
538
|
+
```swift
|
|
539
|
+
func articleExists(withName name: String) -> Bool {
|
|
540
|
+
let fetchRequest = Article.fetchRequest()
|
|
541
|
+
fetchRequest.predicate = NSPredicate(format: "name == %@", name)
|
|
542
|
+
fetchRequest.fetchLimit = 1
|
|
543
|
+
fetchRequest.resultType = .countResultType
|
|
544
|
+
|
|
545
|
+
let count = (try? context.count(for: fetchRequest)) ?? 0
|
|
546
|
+
return count > 0
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
## Performance Tips
|
|
551
|
+
|
|
552
|
+
### ❌ Don't Fetch Everything
|
|
553
|
+
|
|
554
|
+
```swift
|
|
555
|
+
// Bad: Fetches all properties, all objects
|
|
556
|
+
let articles = try context.fetch(Article.fetchRequest())
|
|
557
|
+
let count = articles.count
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### ✅ Use Count Request
|
|
561
|
+
|
|
562
|
+
```swift
|
|
563
|
+
// Good: Only counts, doesn't fetch objects
|
|
564
|
+
let count = try context.count(for: Article.fetchRequest())
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
### ❌ Don't Access Relationships in Loops
|
|
568
|
+
|
|
569
|
+
```swift
|
|
570
|
+
// Bad: Fires fault for each article
|
|
571
|
+
for article in articles {
|
|
572
|
+
print(article.category?.name) // Fault!
|
|
573
|
+
}
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### ✅ Prefetch Relationships
|
|
577
|
+
|
|
578
|
+
```swift
|
|
579
|
+
// Good: Prefetches all categories at once
|
|
580
|
+
let fetchRequest = Article.fetchRequest()
|
|
581
|
+
fetchRequest.relationshipKeyPathsForPrefetching = ["category"]
|
|
582
|
+
let articles = try context.fetch(fetchRequest)
|
|
583
|
+
|
|
584
|
+
for article in articles {
|
|
585
|
+
print(article.category?.name) // No fault!
|
|
586
|
+
}
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
### ❌ Don't Fetch in Loops
|
|
590
|
+
|
|
591
|
+
```swift
|
|
592
|
+
// Bad: Multiple fetch requests
|
|
593
|
+
for name in names {
|
|
594
|
+
let fetchRequest = Article.fetchRequest()
|
|
595
|
+
fetchRequest.predicate = NSPredicate(format: "name == %@", name)
|
|
596
|
+
let articles = try? context.fetch(fetchRequest)
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### ✅ Use IN Predicate
|
|
601
|
+
|
|
602
|
+
```swift
|
|
603
|
+
// Good: Single fetch request
|
|
604
|
+
let fetchRequest = Article.fetchRequest()
|
|
605
|
+
fetchRequest.predicate = NSPredicate(format: "name IN %@", names)
|
|
606
|
+
let articles = try context.fetch(fetchRequest)
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
## Debugging Fetch Requests
|
|
610
|
+
|
|
611
|
+
### Enable SQL Debug
|
|
612
|
+
|
|
613
|
+
Add launch argument:
|
|
614
|
+
```
|
|
615
|
+
-com.apple.CoreData.SQLDebug 1
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
**Output:**
|
|
619
|
+
```sql
|
|
620
|
+
CoreData: sql: SELECT Z_PK, ZNAME, ZVIEWS FROM ZARTICLE WHERE ZVIEWS > ? ORDER BY ZCREATIONDATE DESC LIMIT 20
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### Measure Fetch Performance
|
|
624
|
+
|
|
625
|
+
```swift
|
|
626
|
+
let startTime = CFAbsoluteTimeGetCurrent()
|
|
627
|
+
let articles = try context.fetch(fetchRequest)
|
|
628
|
+
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
|
|
629
|
+
print("Fetch took \(timeElapsed) seconds")
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
## Summary
|
|
633
|
+
|
|
634
|
+
1. **Use `propertiesToFetch`** to limit fetched properties
|
|
635
|
+
2. **Set `fetchBatchSize`** for large datasets (typically 20-50)
|
|
636
|
+
3. **Use `fetchLimit`** when you only need a few results
|
|
637
|
+
4. **Always specify sort descriptors** for predictable results
|
|
638
|
+
5. **Use predicates** to filter at the database level
|
|
639
|
+
6. **Use `NSFetchedResultsController`** for list views
|
|
640
|
+
7. **Prefetch relationships** to avoid N+1 queries
|
|
641
|
+
8. **Use count requests** instead of fetching for counts
|
|
642
|
+
9. **Use aggregate expressions** for statistics
|
|
643
|
+
10. **Enable SQL debug** to understand query performance
|