SwiftData Fetching Pending Changes
source link: https://useyourloaf.com/blog/swiftdata-fetching-pending-changes/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
When you fetch data using Core Data it includes pending changes by default. SwiftData, in theory, works the same way let’s see how it works in practise.
Fetching Pending Changes
Here’s a SwiftData fetch request with a predicate that returns all visited country model objects, sorted by name:
let descriptor = FetchDescriptor<Country>(
predicate: #Predicate { $0.visited },
sortBy: [SortDescriptor(\.name,
comparator: .localizedStandard)])
let countries = try context.fetch(descriptor)
The Core Data SQLDebug flag also works for SwiftData so we can see the SELECT
statement and result this generates:
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZAREA, t0.ZCAPITAL,
t0.ZCONTINENT, t0.ZCURRENCY, t0.ZNAME, t0.ZPOPULATION, t0.ZVISITED,
FROM ZCOUNTRY t0 WHERE t0.ZVISITED = ? ORDER BY t0.ZNAME COLLATE
NSCollateFinderlike
CoreData: annotation: sql connection fetch time: 0.0004s
CoreData: annotation: total fetch execution time: 0.0005s for 5 rows.
I have five countries in my store that have the visited flag set to true
, the SQL query returns five rows which I get back in my countries
array, sorted by name:
countries.map { $0.name }
// ["Belgium", "France", "Germany", "Italy", "United Kingdom"]
What happens if I have pending changes? I’ll repeat the above fetch request after setting the visited flag for another country (Spain), but before saving the context. The SQLDebug log still shows 5 rows returned:
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZAREA, t0.ZCAPITAL, ...
...
CoreData: annotation: total fetch execution time: 0.0005s for 5 rows.
But the result returned to me now contains 6 countries because it includes the unsaved model object for “Spain”:
// ["Belgium", "France", "Germany", "Italy", "Spain", "United Kingdom"]
Note that the results are still returned in sorted order and the pending changes are only included if they match the predicate of the fetch request.
Excluding Pending Changes
If you don’t want the pending changes included in the fetch results you should be able to override the default by setting includePendingChanges
to false
in the fetch descriptor:
var descriptor = FetchDescriptor<Country>(
predicate: #Predicate { $0.visited },
sortBy: [SortDescriptor(\.name,
comparator: .localizedStandard)])
descriptor.includePendingChanges = false
Unfortunately, I don’t seem to be able to get that to work using iOS 17.2 (FB13509125). The pending change is always returned in the results.
For comparison, the Core Data fetch request works as expected, only returning results from the store:
let request = Country.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "name"
ascending: true)]
request.predicate = NSPredicate(format: "%K == true",
#keyPath(Country.visited))
request.includesPendingChanges = false
let countries = try context.fetch(request)
FetchLimit
The way pending changes interacts with a fetch limit also seems a little odd with SwiftData. The fetchLimit
, as the name suggests, puts an upper bound on the number of results returned. Repeating my previous request with a fetch limit of 1:
var descriptor = FetchDescriptor<Country>(
predicate: #Predicate { $0.visited },
sortBy: [SortDescriptor(\.name,
comparator: .localizedStandard)])
descriptor.fetchLimit = 1
let countries = try context.fetch(descriptor)
When using a SQLite store, the fetchLimit becomes a LIMIT
clause on the SELECT
statement:
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZAREA, t0.ZCAPITAL,
t0.ZCONTINENT, t0.ZCURRENCY, t0.ZNAME, t0.ZPOPULATION, t0.ZVISITED,
FROM ZCOUNTRY t0 WHERE t0.ZVISITED = ? ORDER BY t0.ZNAME
COLLATE NSCollateFinderlike LIMIT 1
CoreData: annotation: sql connection fetch time: 0.0003s
CoreData: annotation: total fetch execution time: 0.0004s for 1 rows.
With no pending changes, this works as expected and returns a single row. However, if I have a pending change that matches the predicate:
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZAREA, t0.ZCAPITAL,
t0.ZCONTINENT, t0.ZCURRENCY, t0.ZNAME, t0.ZPOPULATION, t0.ZVISITED,
FROM ZCOUNTRY t0 WHERE t0.ZVISITED = ? ORDER BY t0.ZNAME
COLLATE NSCollateFinderlike LIMIT 2
CoreData: annotation: sql connection fetch time: 0.0002s
CoreData: annotation: total fetch execution time: 0.0003s for 2 rows.
My fetch request still has a fetchLimit
of 1 but the SQL SELECT statement has a LIMIT
of 2 and I get back two results:
// ["Belgium", "Spain"]
It seems that SwiftData is including the pending change in the result without taking into account the fetch limit. But this time if I configure the fetch descriptor to exclude pending changes it seems to work as expected:
descriptor.includePendingChanges = false
The LIMIT
is still 2
in the SELECT statement but the result only contains a single item, excluding the pending change:
// ["Belgium"]
I’m not sure how likely this is to cause a problem in practise but it’s certainly unexpected (FB13509173).
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK