Step:
Create and persist a model.
Encode its persistentModelID using JSONEncoder.
Decode the encoded data back into a PersistentIdentifier.
Use the decoded identifier in a FetchDescriptor predicate to fetch the model.
Modify a property of the model without saving the context.
Execute the exact same fetch again, using the same decoded PersistentIdentifier and the same predicate.
Example:
do {
let item = Item()
modelContext.insert(item)
try modelContext.save()
// Encode and decode the persistent identifier.
// The decoded identifier is essential to reproduce the issue.
let data = try JSONEncoder().encode(item.persistentModelID)
let decodedPID = try JSONDecoder().decode(
PersistentIdentifier.self,
from: data
)
func find(_ pid: PersistentIdentifier) throws -> Item? {
let descriptor = FetchDescriptor<Item>(
predicate: #Predicate {
$0.persistentModelID == pid
}
)
return try modelContext.fetch(descriptor).first
}
// First fetch succeeds.
var result = try find(decodedPID)
print("0. Found: \(result != nil)") // true
// Modify the persisted model without saving the context.
item.timestamp = .now.addingTimeInterval(-3600)
// Second fetch uses exactly the same PID and predicate,
// but unexpectedly fails.
result = try find(decodedPID)
print("1. Found: \(result != nil)") // false
} catch {
print(error)
}Actual Behavior:
The first fetch successfully finds the model.
After modifying one of its properties without saving the context, the second fetch returns an empty result, even though:
The same ModelContext is used.
The same decoded PersistentIdentifier is used.
The same predicate is used.
The model has not been deleted.
No save operation has occurred between the two fetches.
Expected Behavior:
The second fetch should return the same model.
Changing an ordinary property of a model without saving should not invalidate its persistent identity or cause a predicate matching that identity to stop finding the model.
Additional Observations:
If the original item.persistentModelID is used directly instead of the decoded identifier, the issue does not occur.
If the model is not modified between the two fetches, both fetches succeed.
Questions:
Is it expected that a PersistentIdentifier restored through Codable can be used in a SwiftData predicate?
Why does a predicate using a decoded PersistentIdentifier stop matching a model after an unsaved property or relationship change?
Is this a known issue or a limitation of using persistentModelID inside a #Predicate?
What is the recommended way to persist a PersistentIdentifier and subsequently use it to fetch the corresponding model across different operations or app launches?
Topic:
App & System Services
SubTopic:
SwiftData
0
0
19