From 0f26d50e089e0cf9623ccba3280f65a15aa2cc11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=E2=80=99Aquino?= Date: Fri, 29 Aug 2025 15:57:25 -0700 Subject: [PATCH] Prevent publishing changes to Observable outside the main thread MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Daniel D’Aquino --- damus/Features/Events/Models/EventsModel.swift | 7 ++++++- .../FollowPack/Models/FollowPackModel.swift | 7 +++++-- .../NIP05/Models/NIP05DomainEventsModel.swift | 15 ++++++++++----- damus/Features/Search/Models/SearchModel.swift | 3 ++- damus/Features/Timeline/Models/HomeModel.swift | 3 ++- damus/Shared/Utilities/EventHolder.swift | 6 +++++- 6 files changed, 30 insertions(+), 11 deletions(-) diff --git a/damus/Features/Events/Models/EventsModel.swift b/damus/Features/Events/Models/EventsModel.swift index 381a6270..4c586895 100644 --- a/damus/Features/Events/Models/EventsModel.swift +++ b/damus/Features/Events/Models/EventsModel.swift @@ -78,7 +78,11 @@ class EventsModel: ObservableObject { event = ev.toOwned() } guard let event else { return } - if events.insert(event) { objectWillChange.send() } + Task { + if await events.insert(event) { + DispatchQueue.main.async { self.objectWillChange.send() } + } + } case .eose: break } @@ -93,6 +97,7 @@ class EventsModel: ObservableObject { loadingTask?.cancel() } + @MainActor private func handle_event(relay_id: RelayURL, ev: NostrEvent) { if events.insert(ev) { objectWillChange.send() diff --git a/damus/Features/FollowPack/Models/FollowPackModel.swift b/damus/Features/FollowPack/Models/FollowPackModel.swift index b542b50c..5ead6f5b 100644 --- a/damus/Features/FollowPack/Models/FollowPackModel.swift +++ b/damus/Features/FollowPack/Models/FollowPackModel.swift @@ -25,6 +25,7 @@ class FollowPackModel: ObservableObject { func subscribe(follow_pack_users: [Pubkey]) { loading = true + self.listener?.cancel() self.listener = Task { await self.listenForUpdates(follow_pack_users: follow_pack_users) } @@ -52,8 +53,10 @@ class FollowPackModel: ObservableObject { guard let event else { return } if event.is_textlike && should_show_event(state: damus_state, ev: event) && !event.is_reply() { - if self.events.insert(event) { - self.objectWillChange.send() + if await self.events.insert(event) { + DispatchQueue.main.async { + self.objectWillChange.send() + } } } case .eose: diff --git a/damus/Features/NIP05/Models/NIP05DomainEventsModel.swift b/damus/Features/NIP05/Models/NIP05DomainEventsModel.swift index 8c0d4cc5..55a86675 100644 --- a/damus/Features/NIP05/Models/NIP05DomainEventsModel.swift +++ b/damus/Features/NIP05/Models/NIP05DomainEventsModel.swift @@ -67,18 +67,21 @@ class NIP05DomainEventsModel: ObservableObject { for await item in state.nostrNetwork.reader.subscribe(filters: [filter]) { switch item { case .event(borrow: let borrow): - try? borrow { event in - self.add_event(event.toOwned()) + var event: NostrEvent? = nil + try? borrow { ev in + event = ev.toOwned() guard let txn = NdbTxn(ndb: state.ndb) else { return } load_profiles(context: "search", load: .from_events(self.events.all_events), damus_state: state, txn: txn) } + guard let event else { return } + await self.add_event(event) case .eose: continue } } } - func add_event(_ ev: NostrEvent) { + func add_event(_ ev: NostrEvent) async { if !event_matches_filter(ev, filter: filter) { return } @@ -87,8 +90,10 @@ class NIP05DomainEventsModel: ObservableObject { return } - if self.events.insert(ev) { - objectWillChange.send() + if await self.events.insert(ev) { + DispatchQueue.main.async { + self.objectWillChange.send() + } } } } diff --git a/damus/Features/Search/Models/SearchModel.swift b/damus/Features/Search/Models/SearchModel.swift index 0de7e293..f2a9229c 100644 --- a/damus/Features/Search/Models/SearchModel.swift +++ b/damus/Features/Search/Models/SearchModel.swift @@ -49,7 +49,7 @@ class SearchModel: ObservableObject { try? borrow { ev in let event = ev.toOwned() if event.is_textlike && event.should_show_event { - self.add_event(event) + Task { await self.add_event(event) } } } case .eose: @@ -67,6 +67,7 @@ class SearchModel: ObservableObject { listener = nil } + @MainActor func add_event(_ ev: NostrEvent) { if !event_matches_filter(ev, filter: search) { return diff --git a/damus/Features/Timeline/Models/HomeModel.swift b/damus/Features/Timeline/Models/HomeModel.swift index d9566364..ab0649e4 100644 --- a/damus/Features/Timeline/Models/HomeModel.swift +++ b/damus/Features/Timeline/Models/HomeModel.swift @@ -765,6 +765,7 @@ class HomeModel: ContactsDelegate { } } + @MainActor func insert_home_event(_ ev: NostrEvent) { if events.insert(ev) { handle_last_event(ev: ev, timeline: .home) @@ -798,7 +799,7 @@ class HomeModel: ContactsDelegate { switch context { case .home: - insert_home_event(ev) + Task { await insert_home_event(ev) } case .notifications: handle_notification(ev: ev) case .dms, .contacts, .initialRelayList, .initialContactList, .nwc: diff --git a/damus/Shared/Utilities/EventHolder.swift b/damus/Shared/Utilities/EventHolder.swift index 14433964..868144ae 100644 --- a/damus/Shared/Utilities/EventHolder.swift +++ b/damus/Shared/Utilities/EventHolder.swift @@ -38,6 +38,7 @@ class EventHolder: ObservableObject, ScrollQueue { self.incoming = self.incoming.filter(isIncluded) } + @MainActor func insert(_ ev: NostrEvent) -> Bool { if should_queue { return insert_queued(ev) @@ -46,6 +47,7 @@ class EventHolder: ObservableObject, ScrollQueue { } } + @MainActor private func insert_immediate(_ ev: NostrEvent) -> Bool { if has_event.contains(ev.id) { return false @@ -86,7 +88,9 @@ class EventHolder: ObservableObject, ScrollQueue { } if changed { - self.objectWillChange.send() + DispatchQueue.main.async { + self.objectWillChange.send() + } } self.incoming = []