@@ -18,13 +18,13 @@ actor ProfilesRepository {
1818 private var cachedSelf : DBSelfProfile ?
1919 private var warmedUp : Bool = false
2020
21- private let changesSubject : PassthroughSubject < String , Never > = . init( )
21+ private let changesRelay : ProfileChangesRelay = . init( )
2222
2323 /// Emits the `inboxId` whose identity or avatar changed. Repos and read
2424 /// models subscribe to invalidate; the reactive per-inbox publishers are
2525 /// added at the cutover when the ViewModels need them.
2626 nonisolated var profileChanges : AnyPublisher < String , Never > {
27- changesSubject . eraseToAnyPublisher ( )
27+ changesRelay . subject . eraseToAnyPublisher ( )
2828 }
2929
3030 init (
@@ -128,7 +128,7 @@ actor ProfilesRepository {
128128 }
129129
130130 if changed {
131- changesSubject . send ( event. inboxId)
131+ changesRelay . subject . send ( event. inboxId)
132132 }
133133 }
134134
@@ -139,7 +139,7 @@ actor ProfilesRepository {
139139 let updated = edit. applied ( to: existing, updatedAt: Date ( ) )
140140 try await selfProfileStore. save ( updated)
141141 cachedSelf = updated
142- changesSubject . send ( selfInboxId)
142+ changesRelay . subject . send ( selfInboxId)
143143 }
144144
145145 /// Drops a conversation's avatar slots from every person's cache and the
@@ -155,3 +155,11 @@ actor ProfilesRepository {
155155 }
156156 }
157157}
158+
159+ /// Holds the change subject so the actor can vend it from a nonisolated context.
160+ /// `PassthroughSubject` is not `Sendable`, so it cannot be an actor-isolated
161+ /// `let` read from `nonisolated`. This box is safe: the subject is only sent to
162+ /// from inside the actor and only read (to build the publisher) via `subject`.
163+ private final class ProfileChangesRelay : @unchecked Sendable {
164+ let subject : PassthroughSubject < String , Never > = . init( )
165+ }
0 commit comments