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(_:)