Search code examples
iosswiftcombine

Awaiting value of combine publisher


I have a class that manages a websocket connection, it contains some function that sends data to the server:

func sendRequest() async throws -> // ...

Further the class has a @Published property connectionStatus, which shows if a request can be sent:

@Published private(set) var connectionStatus: ConnectionStatus = .notConnected

If there is no connection (i.e. connectionStatus is .notConnected), the sendRequest function throws. If it is .connected, it runs normally. If connectionStatus is .connecting however, I want to wait (await?) at the start of the function until connectionStatus is either connected or disconnected.

Is there some simple way, using combine to achieve this behaviour? Basically await until a combine publisher turns on a specific value for the first time.

I don't want to check every time if the server is connected or not before trying to send a request, so I need for the caller to be able to call this function independently of connectionStatus.


Solution

  • You can convert the publisher to an AsyncSequence using values. Then find the next value it emits using its async iterator.

    @Published private(set) var connectionStatus: ConnectionStatus = .notConnected
    
    func sendRequest() async throws {
        if connectionStatus == .notConnected {
            throw SomeError()
        }
        if connectionStatus == .connecting {
            var iter = $connectionStatus.values.makeAsyncIterator()
            let next = await iter.next()
            if next != .connected {
                throw SomeError()
            }
        }
    }
    

    If sendRequest is called by another thread, I think it is possible that connectionStatus could be changed from .connecting to .connected after the connectionStatus == .connecting, but before $connectionStatus.values.makeAsyncIterator(), causing the async sequence to "miss" the .connected value. Since values can only be published from the main actor, I'd suggest isolating sendRequest to the main actor too.