ListObjectObserver callbacks are not triggered after transaction.deleteAll #380

Open
opened 2025-12-29 18:26:17 +01:00 by adam · 4 comments
Owner

Originally created by @oligazar on GitHub (Nov 6, 2021).

After using transaction.deleteAll inside asynchronous I don't get any ListObjectObserver events anymore.

So in the following code snippet after fetching any new page from API I add new items to the db. But once I need to restart pagination from the page 1, I want to remove all the outdated data (it's controlled by forceRefresh flag). This operation seems to run successfully, meaning I do get listMonitorDidChange callback invocation, which I'm interested in. But all the subsequent inserts with new pages data don't trigger any ListObjectObserver callbacks anymore.

I need some hint to figure out what can cause the issue because so far I'm very frustrated by it.
Thanks a lot!

Database.stack.perform(
                    asynchronous: { transaction -> (SynchronizationStatus, [User]?) in
                        
                        var modified: Bool = false
                        var r_users: [User] = []
                        
                        // For all successful results.
                        let apiUsers: [APIMinimalUserProfile] = apiResponse.users.values()
                        for apiUser in apiUsers {
                            // UPDATE: User exists already by Id.
                            if let t_user: User = try! transaction.fetchOne(by: apiUser.id) {
                                // User get's update.
                                let result = t_user.update(from: apiUser, in: transaction)
                                if case .done = result {
                                    modified = true
                                } else if case .failed(_) = result {
                                    t_user.refreshAndMerge()
                                }

                                // Return this instance.
                                r_users.append(t_user)
                            } else {
                                // CREATE: User not exists yet.
                                let (result, t_user) = User.make(from: apiUser, in: transaction)
                                if case .done = result, let t_user = t_user {
                                    modified = true

                                    // Return this instance.
                                    r_users.append(t_user)
                                }
                            }
                        }
                        
                        if (forceRefetch) { // if `forceRefetch` is true then we delete all but the first page items 
                            var ids = r_users.map { $0.id }
                            ids.append(me.user.id)
                            if try transaction.deleteAll(From<User>(), notIn: ids) > 0 {
                                modified = true
                            }
                        }
                        
                        return (modified ? .ok : .notModified, r_users)
                    },
                    success: { status, r_users in
                        self.workerQueue.async {
                            // Try store header data for later If-Modified-Since and If-None-Match requests. Or remove stored data if no suitable headers are present.
                            try? self.record(ConditionalHeaders(headers), for: requestId)
                            
                            // Pass the objects as is, they have to be refetched before using them outside the transaction.
                            completeResponse(status, r_users, apiResponse.total)
                        }
                    },
                    failure: { error in
                        print(error)
                        self.workerQueue.async {
                            completeResponse(.failure(error), nil, apiResponse.total)
                        }
                    }
                )

Originally created by @oligazar on GitHub (Nov 6, 2021). After using `transaction.deleteAll` inside `asynchronous` I don't get any `ListObjectObserver` events anymore. So in the following code snippet after fetching any new page from API I add new items to the db. But once I need to restart pagination from the page 1, I want to remove all the outdated data (it's controlled by `forceRefresh` flag). This operation seems to run successfully, meaning I do get `listMonitorDidChange` callback invocation, which I'm interested in. But all the subsequent inserts with new pages data don't trigger any `ListObjectObserver` callbacks anymore. I need some hint to figure out what can cause the issue because so far I'm very frustrated by it. Thanks a lot! ``` Database.stack.perform( asynchronous: { transaction -> (SynchronizationStatus, [User]?) in var modified: Bool = false var r_users: [User] = [] // For all successful results. let apiUsers: [APIMinimalUserProfile] = apiResponse.users.values() for apiUser in apiUsers { // UPDATE: User exists already by Id. if let t_user: User = try! transaction.fetchOne(by: apiUser.id) { // User get's update. let result = t_user.update(from: apiUser, in: transaction) if case .done = result { modified = true } else if case .failed(_) = result { t_user.refreshAndMerge() } // Return this instance. r_users.append(t_user) } else { // CREATE: User not exists yet. let (result, t_user) = User.make(from: apiUser, in: transaction) if case .done = result, let t_user = t_user { modified = true // Return this instance. r_users.append(t_user) } } } if (forceRefetch) { // if `forceRefetch` is true then we delete all but the first page items var ids = r_users.map { $0.id } ids.append(me.user.id) if try transaction.deleteAll(From<User>(), notIn: ids) > 0 { modified = true } } return (modified ? .ok : .notModified, r_users) }, success: { status, r_users in self.workerQueue.async { // Try store header data for later If-Modified-Since and If-None-Match requests. Or remove stored data if no suitable headers are present. try? self.record(ConditionalHeaders(headers), for: requestId) // Pass the objects as is, they have to be refetched before using them outside the transaction. completeResponse(status, r_users, apiResponse.total) } }, failure: { error in print(error) self.workerQueue.async { completeResponse(.failure(error), nil, apiResponse.total) } } ) ```
Author
Owner

@JohnEstropia commented on GitHub (Nov 8, 2021):

@oligazar I don't see anything that stands out from the snippet you posted, but some ideas that come to mind:

  • Check if the new objects you save still match the original Where clause used by the ListMonitor
  • Check if the ListObjectObserver is still subscribed to changes (removeObserver(_:) is not called anywhere in between)
  • Check if the ListMonitor is still retained in the first place
@JohnEstropia commented on GitHub (Nov 8, 2021): @oligazar I don't see anything that stands out from the snippet you posted, but some ideas that come to mind: - Check if the new objects you save still match the original `Where` clause used by the `ListMonitor` - Check if the `ListObjectObserver` is still subscribed to changes (`removeObserver(_:)` is not called anywhere in between) - Check if the `ListMonitor` is still retained in the first place
Author
Owner

@oligazar commented on GitHub (Nov 8, 2021):

@JohnEstropia thanks for your reply!

  • ListMonitor is still retained, and its observer as well. I compared hashValues before and after.
  • Where clause seems to be the same (it's not mine code so there's possibility that I'm missing something)
  • I can't see a way to check observers list on the monitor object directly. But I cannot see any explicit invocations of removeObserver(_:) on it. But I think it's the most probable issue among others.

Maybe there's a way to check whether monitor is still holding a reference to observer? Or something different to check?

@oligazar commented on GitHub (Nov 8, 2021): @JohnEstropia thanks for your reply! * `ListMonitor` is still retained, and its observer as well. I compared hashValues before and after. * `Where` clause seems to be the same (it's not mine code so there's possibility that I'm missing something) * I can't see a way to check observers list on the monitor object directly. But I cannot see any explicit invocations of `removeObserver(_:)` on it. But I think it's the most probable issue among others. Maybe there's a way to check whether `monitor` is still holding a reference to observer? Or something different to check?
Author
Owner

@JohnEstropia commented on GitHub (Nov 8, 2021):

Where clause seems to be the same

Yes, but what I mean was, are the new objects that are inserted afterwards still satisfying this predicate?

Maybe there's a way to check whether monitor is still holding a reference to observer?

To clarify, the ListMonitor only keeps weak references to its observers. So don't expect it to retain the observer for you.

Otherwise, there must be something else going on that breaks the notification chain. You can test the CoreStore demo app (example titled Classic Colors Demo) where objects can be deleted at once and still continue to receive succeeding updates.

@JohnEstropia commented on GitHub (Nov 8, 2021): > `Where` clause seems to be the same Yes, but what I mean was, are the new objects that are inserted afterwards still satisfying this predicate? > Maybe there's a way to check whether `monitor` is still holding a reference to observer? To clarify, the `ListMonitor` only keeps weak references to its observers. So don't expect it to retain the observer for you. Otherwise, there must be something else going on that breaks the notification chain. You can test the CoreStore demo app (example titled `Classic Colors Demo`) where objects can be deleted at once and still continue to receive succeeding updates.
Author
Owner

@oligazar commented on GitHub (Nov 8, 2021):

Yes, but what I mean was, are the new objects that are inserted afterwards still satisfying this predicate?

Yes, new objects are still satisfying the predicate

To clarify, the ListMonitor only keeps weak references to its observers. So don't expect it to retain the observer for you.

So reinstantiating the monitor should resolve the issue, I guess?

I believe the demo app works as expected. The issue is most likely in my code. I just struggle to figure it out for a few days now.

@oligazar commented on GitHub (Nov 8, 2021): > Yes, but what I mean was, are the new objects that are inserted afterwards still satisfying this predicate? Yes, new objects are still satisfying the predicate > To clarify, the `ListMonitor` only keeps weak references to its observers. So don't expect it to retain the observer for you. So reinstantiating the monitor should resolve the issue, I guess? I believe the demo app works as expected. The issue is most likely in my code. I just struggle to figure it out for a few days now.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/CoreStore-JohnEstropia#380