Hassan Shahbazi
Hassan Shahbazi

Reputation: 1603

Loop over Publisher Combine framework

I have the following function to perform an URL request:

final class ServiceManagerImpl: ServiceManager, ObservableObject {
    private let session = URLSession.shared

    func performRequest<T>(_ request: T) -> AnyPublisher<String?, APIError> where T : Request {
        session.dataTaskPublisher(for: self.urlRequest(request))
            .tryMap { data, response in
                try self.validateResponse(response)
                return String(data: data, encoding: .utf8)
            }
            .mapError { error in
                return self.transformError(error)
            }
            .eraseToAnyPublisher()
    }
}

Having these 2 following functions, I can now call the desired requests from corresponded ViewModel:

final class AuditServiceImpl: AuditService {
    private let serviceManager: ServiceManager = ServiceManagerImpl()

    func emptyAction() -> AnyPublisher<String?, APIError> {
        let request = AuditRequest(act: "", nonce: String.randomNumberGenerator)
        return serviceManager.performRequest(request)
    }

    func burbleAction(offset: Int) -> AnyPublisher<String?, APIError> {
        let request = AuditRequest(act: "burble", nonce: String.randomNumberGenerator, offset: offset)
        return serviceManager.performRequest(request)
    }
}
final class AuditViewModel: ObservableObject {
    @Published var auditLog: String = ""
    private let auditService: AuditService = AuditServiceImpl()

    init() {
        let timer = Timer(timeInterval: 5, repeats: true) { _ in
            self.getBurbles()
        }
        RunLoop.main.add(timer, forMode: .common)
    }

    func getBurbles() {
        auditService.emptyAction()
            .flatMap { [unowned self] offset -> AnyPublisher<String?, APIError> in
                let currentOffset = Int(offset?.unwrapped ?? "") ?? 0
                return self.auditService.burbleAction(offset: currentOffset)
            }
            .receive(on: RunLoop.main)
            .sink(receiveCompletion: { [unowned self] completion in
                print(completion)
            }, receiveValue: { [weak self] burbles in
                self?.auditLog = burbles!
            })
            .store(in: &cancellableSet)
    }
}

Everything is fine when I use self.getBurbles() for the first time. However, for the next calls, print(completion) shows finished, and the code doesn't perform self?.auditLog = burbles!

I don't know how can I loop over the getBurbles() function and get the response at different intervals.

Edit

The whole process in a nutshell:

Upvotes: 2

Views: 1415

Answers (1)

Josh Homann
Josh Homann

Reputation: 16347

There are at least 2 issues here.

First when a Publisher errors it will never produce elements again. That's a problem here because you want to recycle the Publisher here and call it many times, even if the inner Publisher fails. You need to handle the error inside the flatMap and make sure it doesn't propagate to the enclosing Publisher. (ie you can return a Result or some other enum or tuple that indicates you should display an error state).

Second, flatMap is almost certainly not what you want here since it will merge all of the api calls and return them in arbitrary order. If you want to cancel any existing requests and only show the latest results then you should use .map followed by switchToLatest.

Upvotes: 1

Related Questions