I try to implement some repository that can notify about change its state.
protocol Observer {
}
actor Repository: Actor {
var observers: [Observer] = []
func add(observer: Observer) {
self.observers.append(observer)
}
}
and add unit test for it
func test_get_update() async {
let repository = Repository()
let observer = MockObserver()
await repository.add(observer: observer)
}
and get warning
Non-sendable type 'any Observer' passed in implicitly asynchronous call to actor-isolated instance method 'add(observer:)' cannot cross actor boundary
but in main app there is no warning.
class Bar {
func foo() async {
let repository = Repository()
let observer = MockObserver()
await repository.add(observer: observer)
}
}
I don't understand is this implementation of repository correct or not?
You could certainly make your Observer
a Sendable
type:
protocol Observer: Sendable {
func observe(something: Int)
}
struct MockObserver: Observer {
func observe(something: Int) {
print("I observed \(something)")
}
}
actor Repository {
private var observers: [any Observer] = []
func add(observer: any Observer) {
observers.append(observer)
}
private func notifyObservers(value: Int) {
for observer in observers {
observer.observe(something: value)
}
}
func doSomething() {
notifyObservers(value: 42)
}
}
let myRepo = Repository()
Task {
let myObserver = MockObserver()
await myRepo.add(observer: myObserver)
await myRepo.doSomething()
}
But since Sendable
imposes so many restrictions on things, that might make implementing an interesting observer a challenge.
I would be inclined to use the Observer
implementations already available in the Combine framework:
actor AnotherRepo {
nonisolated let interestingState: AnyPublisher<Int, Never>;
private let actualState = PassthroughSubject<Int, Never>();
init() {
interestingState = actualState.eraseToAnyPublisher()
}
func doSomething() {
actualState.send(42)
}
}
let anotherRepo = AnotherRepo()
anotherRepo.interestingState.sink { print("I also observed \($0)") }
Task {
await anotherRepo.doSomething()
}