Swift Combine. How to transform publisher values

Viewed 6598

I need to download files using provided links from the backend. To download files the asynchronous API is used that returns Progress() object. The problem is that FlatMap cannot map from Publisher<[Link], Error> to Publisher<[File], Error>. Another problem I want to solve is getting rid of cancellable and somehow transform Progress info filePath once the Progress.fractionCompleted is equal to 1.0.

For now I have tried to use map function. See the code form the Playground:

import UIKit
import Combine

var progress = Progress()
extension ProgressUserInfoKey {
    public static var destinationURL = ProgressUserInfoKey("destinationURL")
}

func download(from urlRequest: URLRequest, to destinationURL: URL) -> AnyPublisher<Progress, Error> {
    return Future<Progress, Error> { promise in
        progress = Progress(totalUnitCount: 1)
        progress.setUserInfoObject(destinationURL.absoluteString,
                                   forKey: ProgressUserInfoKey.destinationURL)
        promise(.success(progress))
        // Simulate async API
        DispatchQueue.main.async {
            progress.completedUnitCount = 1
        }
    }.eraseToAnyPublisher()
}

struct Link: Decodable {
    let url: String
}

func getLinks() -> AnyPublisher<[Link], Error> {
    return URLSession.shared.dataTaskPublisher(for: URL(string: "https://backend.com")!)
        .map { $0.data }
        .decode(type: [Link].self, decoder: JSONDecoder())
        .eraseToAnyPublisher()
}

struct File {
    let url: URL
    let size: UInt32
}

private func destinationUrl(_ fromUrl: String?) -> URL {
    guard let path = fromUrl else {
        return URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)
    }
    return URL(fileURLWithPath: path)
}

/// 2) How to get rid of this state and transoft Progress into filePath directly (using matp(transform: ? )
var cancellableSet = Set<AnyCancellable>()

func getFiles() -> AnyPublisher<[File], Error> {
    getLinks()
        .flatMap { (links) -> AnyPublisher<[File], Error> in
            let sequence = Sequence<[AnyPublisher<File, Error>], Error>(sequence: links.map {
                download(from: URLRequest(url: $0), to: destinationUrl(UUID().uuidString))
                .sink { progress in
                    progress.publisher(for: \.fractionCompleted).sink { progressValue in
                        if progressValue == 1.0 {
                            let filePath: String = progress.userInfo[ProgressUserInfoKey.destinationURL] as? String ?? ""
/// 1) How to return publisher here
                            ///return Publisher(File(url: URL(string: filePath)!, size: 0))
                        }
                    }
                    .store(in: &cancellableSet)
                }
                .store(in: &cancellableSet)
            } )
            return sequence.flatMap { $0 }.collect().eraseToAnyPublisher()
    }
}

I am expecting that the code compiles successfully and the function getFiles returns AnyPublisher<[File], Error>.

Currently the error code am getting is the following:

Cannot convert return expression of type 'Publishers.FlatMap<AnyPublisher<[File], Error>, AnyPublisher<[Link], Error>>' to return type 'AnyPublisher<[File], Error>'
1 Answers

Problem has been solved by using Publishers.Sequence() extension and avoiding to call a function sink(). Also I used the flatMap() to transform the Output value. The code that demonstrate this way might be found here: Combine publishers together

Related