2
\$\begingroup\$

I consume a RestApi with few Combine Publishers and want to visually represent ongoing progress with UIActivityIndicatorView. My current logic uses handleEvents->receiveSubscription closure to start an animation and handleEvents->receiveCancel and sink->completion to stop an animation of the activityIndicatorView. I ask for a review because it looks strange that a stop animation must be called from two different closures which looks like I am missing something

    let first = URLSession.shared.dataTaskPublisher(for: .init(string: "http://httpbin.org/delay/10")!)
        .tryMap { (data: Data, response: URLResponse) in
            return data
        }
    
    let second = URLSession.shared.dataTaskPublisher(for: .init(string: "http://httpbin.org/delay/5")!)
        .tryMap { (data: Data, response: URLResponse) in
            return data
        }
    
    cancellable = Publishers.Zip(first,second)
        .flatMap { _ in
            first
        }
        .handleEvents(receiveSubscription: { subscription in
            print("receiveCancel: activityIndicatorView.startAnimating()")
        }, receiveCancel: {
            print("receiveCancel: activityIndicatorView.stopAnimating()")
        })
        .sink { _ in
            print("sink completion: activityIndicatorView.stopAnimating()")
        } receiveValue: { _ in }
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

You have not missed anything. I was surprised when I learned about this too. Consider implementing the using operator from RxSwift. I have already submitted a pull request to CombineExt with the operator.

Once you have implemented the Using type, you can also implement an ActivityTracker

Then your code sample becomes:

let activityTracker = ActivityTracker()

let first = URLSession.shared.dataTaskPublisher(for: .init(string: "http://httpbin.org/delay/10")!)
    .tryMap { (data: Data, response: URLResponse) in
        return data
    }
    .trackActivity(activityTracker)

let second = URLSession.shared.dataTaskPublisher(for: .init(string: "http://httpbin.org/delay/5")!)
    .tryMap { (data: Data, response: URLResponse) in
        return data
    }
    .trackActivity(activityTracker)

Publishers.Zip(first, second)
    .flatMap { _ in first }
    .sink(
        receiveCompletion: { complete in
            print("Handle error but otherwise don't do anything.")
        },
        receiveValue: { values in
            print("Use data here.")
        }
    )
    .store(in: &cancellables)

activityTracker.isActive
    .sink(receiveValue: { isActive in
        if isActive {
            activityIndicatorView.startAnimating()
        }
        else {
            activityIndicatorView.stopAnimating()
        }
    })
    .store(in: &cancellables)

Your activity tracking stream is now it's own thing. No need to retain the activity tracker so it doesn't need to be a property of a class. The publisher chain will retain it and release it when appropriate.

Also, the way your code is constructed, "http://httpbin.org/delay/10" is being called twice. Is that intentional? It seems rather odd.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.