I have a class written in Swift that looks like this:
protocol MessageSubscriber: ObservableObject, Subscriber where Input == Message, Failure == Never {
func cancel()
}
class MySubscriberClass: MessageSubscriber {
@Published var data: [MyDataResponse] = []
private var subscription: Subscription?
func cancel() {
subscription?.cancel()
}
func receive(subscription: Subscription) {
self.subscription = subscription
self.subscription?.request(.unlimited)
}
func receive(_ input: Message) -> Subscribers.Demand {
handleMessage(input)
return .unlimited
}
func receive(completion: Subscribers.Completion<Never>) {}
@MainActor func handleMessage(_ message: Message) {
if case .foo(let bar) = message.msg {
data.removeAll()
for index in bar.foobar.indices {
if !bar.foobar[index].isEmpty {
data.append(MyDataResponse(parameter: bar.foobar[index], index: index))
}
}
}
}
}
A bit of background: This class is used in a view as a @StateObject and it works fine (The @Published data is updated and shown how I want). However, I got some purple triangle warnings
Publishing changes from background threads is not allowed ...
so I added @MainActor to handleMessage(_:) function.
The problem begins after adding @MainActor to handleMessage(_:) function. Now receive(_ input: Message) function is giving an error:
Call to main actor-isolated instance method 'handleMessage' in a synchronous nonisolated context
Sure, let's wrap the handleMessage(_:) call with Task { @MainActor in handleMessage(_:) }. This produces a warning
Capture of 'self' with non-sendable type 'MySubscriberClass' in a '@Sendable' closure.
Note here that I can't mark receive(_ input: Message) with @MainActor because
Main actor-isolated instance method 'receive' cannot be used to satisfy nonisolated protocol requirement
I can try to go the other way and mark the whole class with @MainActor. I believe this is the recommended approach since we are updating the UI from this class. Now I have to mark cancel() receive(subscription: Subscription) receive(_ input: Message) and receive(completion: Subscribers.Completion<Never>) as nonisolated because
Main actor-isolated instance method cannot be used to satisfy nonisolated protocol requirement
Next errors spawn from all the functions I just marked as nonisolated. `
Main actor-isolated property 'subscription' can not be referenced from a non-isolated context
from inside cancel() for example and
Call to main actor-isolated instance method 'handleMessage' in a synchronous nonisolated context
Let's fix those by wrapping everything with Task { @MainActor in }
nonisolated func cancel() {
Task { @MainActor in
subscription?.cancel()
}
}
nonisolated func receive(subscription: Subscription) {
Task { @MainActor in
self.subscription = subscription
self.subscription?.request(.unlimited)
}
}
nonisolated func receive(_ input: Message) -> Subscribers.Demand {
Task { @MainActor in
handleMessage(input)
}
return .unlimited
}
Yet again I have a warning, but this time it comes from the line
self.subscription = subsription inside receive(subscription: Subsription)
Capture of 'subscription' with non-sendable type 'any Subscription' in a '@Sendable' closure
What is the correct approach to make this code free of warnings?
Subscriber protocol is part of the Combine framework. In the view this class is passed as a parameter to a PassthroughSubject<Message, Never>.subscribe(_:)