ioscombineswift-concurrencyswift6

How to convert async function/method to publisher in Swift 6


I tried to make as simple example as possible using Swift 6 to produce this problem which I am having difficulty to solve:

func getValue() async -> Int { 0 }

func getValuePublisher() -> AnyPublisher<Int, Never> {
    Future { continuation in
        Task {
            let value = await getValue()
            continuation(.success(value))
        }
    }.eraseToAnyPublisher()
}

This will produce a compile time error (Swift 6 only) saying

Passing closure as a 'sending' parameter risks causing data races between code in the current task and concurrent execution of the closure

The bottom line is to convert an existing async operation to a publisher in any way. I am sure I just need to use more suitable tools for the job, but I am having trouble finding a correct tool to use.

How can I best to deal with this situation?

Note: The async method is existing and can not be changed. It simply needs to be incorporated somehow into publisher pipeline.


Solution

  • In terms of general description of the error, see the Swift user documentation. The general idea to avoid this sort of error is to make sure the Task is isolated to the same actor that this function uses.

    So, for example, you can isolate the function to a global actor:

    func getValue() async -> Int { 0 }
    
    @MainActor // or use any custom global actor, if you want
    func getValuePublisher() -> AnyPublisher<Int, Never> {
        Future { continuation in
            Task {
                let value = await getValue()
                continuation(.success(value))
            }
        }.eraseToAnyPublisher()
    }