Breakpoint in listMonitorWillChange
All fields are nil, so I can't delete local attachments blobs
Originally created by @spacedema on GitHub (Jan 21, 2022).
I have two entities with 1->M relationship. Note and Attachment (note can have many attachments and the attachment holds link to the note).
<img width="1149" alt="attachments" src="https://user-images.githubusercontent.com/7204319/150509228-c12664ef-11e8-493b-955b-2901e164a151.png">
<img width="1146" alt="notes" src="https://user-images.githubusercontent.com/7204319/150509238-7abb2778-b1c9-4508-8086-0896673fdc5c.png">
I'm using `NSManageObject` and I have AttachmentsDbListener
```
// https://github.com/JohnEstropia/CoreStore/issues/362
class AttachesDbListener {
typealias ListEntityType = Attachments
private let monitor: ListMonitor<Attachments>
var pendingObjects: [NSManagedObjectID: String] = [:]
private let dispatchQueue = DispatchQueue(label: "com.test.AttachesDbListenerQueue")
init(dataStack: DataStack) {
monitor = dataStack.monitorList(
From<Attachments>()
.orderBy(.ascending(\.localPath))
)
monitor.addObserver(self)
}
}
extension AttachesDbListener: ListObjectObserver {
func listMonitorDidChange(_ monitor: ListMonitor<Attachments>) {
pendingObjects = [:]
}
func listMonitorDidRefetch(_ monitor: ListMonitor<Attachments>) {
}
func listMonitorWillChange(_ monitor: ListMonitor<ListEntityType>) {
pendingObjects = monitor.objectsInAllSections().reduce(into: [:]) { $0[$1.objectID] = $1.localPath }
}
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didDeleteObject object: ListEntityType, fromIndexPath indexPath: IndexPath) {
guard let path = pendingObjects[object.objectID] else { return }
log.debug("Will delete attachment with local url \(String(describing: object.localPath))", tag: \.attachmentsRemover)
if let url = URLPathHelper.tryFindCurrentPath(forPath: path)?.toFileURL() {
dispatchQueue.async {
do {
try FileManager.default.removeItem(at: url)
log.debug("Did delete attachment with local url \(path)", tag: \.attachmentsRemover)
} catch let error {
log.error(error.localizedDescription)
}
}
}
}
}
```
Steps to reproduce the issue:
1. Create Note And Attachment in it
2. Close app
3. Open app
4. Delete Note
5. Breakpoint in `listMonitorWillChange`
<img width="1201" alt="Снимок экрана 2022-01-21 в 13 23 40" src="https://user-images.githubusercontent.com/7204319/150511082-c9a81756-1fb6-4a14-89f3-2625c82bb390.png">
All fields are nil, so I can't delete local attachments blobs
@spacedema This seems to be the expected behavior if the objects you access in listMonitorWillChange are in faulted state. At that state, accessing the object (firing its fault) would attempt to fetch values from the persistent store, where it obviously had already been deleted.
When objects had been deleted, consider those objects as tombstones and the only reliable info would be its NSManagedObjectID.
Looking at your use case, I would suggest to keep a cache of object.localPaths and use the NSManagedObjectID as keys. You can then execute your cleanup code anytime later on using the deleted object's objectID.
@JohnEstropia commented on GitHub (Jan 24, 2022):
@spacedema This seems to be the expected behavior if the objects you access in `listMonitorWillChange` are in faulted state. At that state, accessing the object (firing its fault) would attempt to fetch values from the persistent store, where it obviously had already been deleted.
When objects had been deleted, consider those objects as tombstones and the only reliable info would be its `NSManagedObjectID`.
Looking at your use case, I would suggest to keep a cache of `object.localPath`s and use the `NSManagedObjectID` as keys. You can then execute your cleanup code anytime later on using the deleted object's `objectID`.
Looking at your use case, I would suggest to keep a cache of object.localPaths and use the NSManagedObjectID as keys. You can then execute your cleanup code anytime later on using the deleted object's objectID.
@spacedema commented on GitHub (Jan 24, 2022):
> Looking at your use case, I would suggest to keep a cache of object.localPaths and use the NSManagedObjectID as keys. You can then execute your cleanup code anytime later on using the deleted object's objectID.
It looks too complicated. It would be easier if Persistent History Tracking were implemented ([https://developer.apple.com/documentation/coredata/consuming_relevant_store_changes](https://developer.apple.com/documentation/coredata/consuming_relevant_store_changes)). `Preserve after deletion` would solve this problem, wouldn't it?
@spacedema I don't think the Preserve after deletion feature works how you imagine it would. Yes, the attribute value will be saved even for deleted records, but that data belongs to the NSPersistentHistoryTransaction. It will not be propagated to NSManagedObject instances. History transaction records are only used to notify NSManagedObjectContexts of external changes, but faulted objects still fetch data from the store.
Rather than containing full objects under keys like NSUpdatedObjectsKey, it contains object IDs under keys like updated_objectIDs. This is a bit unexpected, because NSManagedObjectContext is already documented to support NSManagedObjectID or NSURL objects under the NSUpdatedObjectsKey key.
In any case, you get IDs because it isn’t storing the changed values. Instead, when merging, it fetches the latest values from the store.
Even when Core Store eventually implements Persistent History Tracking, I don't intend to open up an API to access the raw history transactions as shown in the sample codes. That's for merging diffs into the relevant contexts. Of course you're free to observe relevant NSNotifications at that point, but I doubt it would be less complicated that just managing cleanup tables on your own:
classAttachesDbListener:ListObjectObserver{letmonitor:ListMonitor<Attachments>// ...lazyvarlocalPathsByID:[NSManagedObjectID:String]=self.allPaths()privatefuncallPaths()->[NSManagedObjectID:String]{returnmonitor.objectsInAllSections().reduce(into:[:]){$0[$1.objectID]=$1.localPath}}funclistMonitorDidChange(_monitor:ListMonitor<Attachments>){localPathsByID=self.allPaths()}funclistMonitorDidRefetch(_monitor:ListMonitor<Attachments>){localPathsByID=self.allPaths()}funclistMonitor(_monitor:ListMonitor<ListEntityType>,didDeleteObjectobject:ListEntityType,fromIndexPathindexPath:IndexPath){guardletpath=localPathsByID.removeValue(forKey:object.objectID)else{return}log.debug("Will delete attachment with local url \(String(describing:path))",tag:\.attachmentsRemover)ifleturl=URLPathHelper.tryFindCurrentPath(forPath:path)?.toFileURL(){dispatchQueue.async{do{tryFileManager.default.removeItem(at:url)log.debug("Did delete attachment with local url \(path)",tag:\.attachmentsRemover)}catchleterror{log.error(error.localizedDescription)}}}}}
@JohnEstropia commented on GitHub (Jan 24, 2022):
@spacedema I don't think the `Preserve after deletion` feature works how you imagine it would. Yes, the attribute value will be saved even for deleted records, but that data belongs to the `NSPersistentHistoryTransaction`. It will not be propagated to `NSManagedObject` instances. History transaction records are only used to notify `NSManagedObjectContexts` of external changes, but [faulted objects still fetch data from the store](https://mjtsai.com/blog/2019/08/21/persistent-history-tracking-in-core-data/).
> - Rather than containing full objects under keys like NSUpdatedObjectsKey, it contains object IDs under keys like updated_objectIDs. This is a bit unexpected, because NSManagedObjectContext is already documented to support NSManagedObjectID or NSURL objects under the NSUpdatedObjectsKey key.
> - In any case, you get IDs because it isn’t storing the changed values. Instead, when merging, it fetches the latest values from the store.
Even when Core Store eventually implements Persistent History Tracking, I don't intend to open up an API to access the raw history transactions [as shown in the sample codes](https://developer.apple.com/documentation/coredata/consuming_relevant_store_changes). That's for merging diffs into the relevant contexts. Of course you're free to observe relevant `NSNotification`s at that point, but I doubt it would be less complicated that just managing cleanup tables on your own:
```swift
class AttachesDbListener: ListObjectObserver {
let monitor: ListMonitor<Attachments> // ...
lazy var localPathsByID: [NSManagedObjectID: String] = self.allPaths()
private func allPaths() -> [NSManagedObjectID: String] {
return monitor.objectsInAllSections().reduce(into: [:]) { $0[$1.objectID] = $1.localPath }
}
func listMonitorDidChange(_ monitor: ListMonitor<Attachments>) {
localPathsByID = self.allPaths()
}
func listMonitorDidRefetch(_ monitor: ListMonitor<Attachments>) {
localPathsByID = self.allPaths()
}
func listMonitor(_ monitor: ListMonitor<ListEntityType>, didDeleteObject object: ListEntityType, fromIndexPath indexPath: IndexPath) {
guard let path = localPathsByID.removeValue(forKey: object.objectID) else { return }
log.debug("Will delete attachment with local url \(String(describing: path))", tag: \.attachmentsRemover)
if let url = URLPathHelper.tryFindCurrentPath(forPath: path)?.toFileURL() {
dispatchQueue.async {
do {
try FileManager.default.removeItem(at: url)
log.debug("Did delete attachment with local url \(path)", tag: \.attachmentsRemover)
} catch let error {
log.error(error.localizedDescription)
}
}
}
}
}
```
Ok, got it, thanks for detailed answer.
But I'll still wait for the implementation of Persistent History Tracking in order to use Spotlight with NSCoreDataCoreSpotlightDelegate :)
@spacedema commented on GitHub (Jan 24, 2022):
Ok, got it, thanks for detailed answer.
But I'll still wait for the implementation of Persistent History Tracking in order to use Spotlight with NSCoreDataCoreSpotlightDelegate :)
it's working as expected. Is it expected behaviour?
@spacedema commented on GitHub (Jan 24, 2022):
@JohnEstropia the code above don't work as expected after reopen app, i.e.
1. open app
2. create root object and attachment in it
3. close app
4. open app
5. delete root object, `listMonitorDidChange` and `listMonitorDidRefetch` not called, `localPathsByID` is Empty
But if i wrote
```
init(dataStack: DataStack) {
monitor = dataStack.monitorList(
From<Attachments>()
.orderBy(.ascending(\. localPath))
)
monitor.addObserver(self)
pendingObjects = allPaths()
}
```
it's working as expected. Is it expected behaviour?
Yes, you'd want to get the latest values right after you call addObserver(). My code was just pseudo code 👍
@JohnEstropia commented on GitHub (Jan 24, 2022):
Yes, you'd want to get the latest values right after you call `addObserver()`. My code was just pseudo code 👍
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Originally created by @spacedema on GitHub (Jan 21, 2022).
I have two entities with 1->M relationship. Note and Attachment (note can have many attachments and the attachment holds link to the note).


I'm using
NSManageObjectand I have AttachmentsDbListenerSteps to reproduce the issue:
listMonitorWillChangeAll fields are nil, so I can't delete local attachments blobs
@JohnEstropia commented on GitHub (Jan 24, 2022):
@spacedema This seems to be the expected behavior if the objects you access in
listMonitorWillChangeare in faulted state. At that state, accessing the object (firing its fault) would attempt to fetch values from the persistent store, where it obviously had already been deleted.When objects had been deleted, consider those objects as tombstones and the only reliable info would be its
NSManagedObjectID.Looking at your use case, I would suggest to keep a cache of
object.localPaths and use theNSManagedObjectIDas keys. You can then execute your cleanup code anytime later on using the deleted object'sobjectID.@spacedema commented on GitHub (Jan 24, 2022):
It looks too complicated. It would be easier if Persistent History Tracking were implemented (https://developer.apple.com/documentation/coredata/consuming_relevant_store_changes).
Preserve after deletionwould solve this problem, wouldn't it?@JohnEstropia commented on GitHub (Jan 24, 2022):
@spacedema I don't think the
Preserve after deletionfeature works how you imagine it would. Yes, the attribute value will be saved even for deleted records, but that data belongs to theNSPersistentHistoryTransaction. It will not be propagated toNSManagedObjectinstances. History transaction records are only used to notifyNSManagedObjectContextsof external changes, but faulted objects still fetch data from the store.Even when Core Store eventually implements Persistent History Tracking, I don't intend to open up an API to access the raw history transactions as shown in the sample codes. That's for merging diffs into the relevant contexts. Of course you're free to observe relevant
NSNotifications at that point, but I doubt it would be less complicated that just managing cleanup tables on your own:@spacedema commented on GitHub (Jan 24, 2022):
Ok, got it, thanks for detailed answer.
But I'll still wait for the implementation of Persistent History Tracking in order to use Spotlight with NSCoreDataCoreSpotlightDelegate :)
@spacedema commented on GitHub (Jan 24, 2022):
@JohnEstropia the code above don't work as expected after reopen app, i.e.
listMonitorDidChangeandlistMonitorDidRefetchnot called,localPathsByIDis EmptyBut if i wrote
it's working as expected. Is it expected behaviour?
@JohnEstropia commented on GitHub (Jan 24, 2022):
Yes, you'd want to get the latest values right after you call
addObserver(). My code was just pseudo code 👍@spacedema commented on GitHub (Jan 24, 2022):
Got it, thanks