5

Here is a simple "Download" class to illustrate what I want to do.

class Download {
    
    public var progress: CurrentValueSubject<Double, Never> = CurrentValueSubject<Double, Never>(0)
    
    var subscriptions: Set<AnyCancellable> = []
    
    func start(task: URLSessionTask) {
        task.resume()
        task.progress.publisher(for: \.fractionCompleted).sink { [weak self] (newProgress) in
            self?.progress.send(newProgress)
        }.store(in: &subscriptions)
    }
    
}

I would like to be able to "re-publish" the progress property observer publisher to my current value subject. As you can see I currently subscribe using the .sink function and then just call the CurrentValueSubject publisher directly.

I would like to be able to use something like the .assign(to:, on:) operator like this.

task.progress.publisher(for: \.fractionCompleted).assign(to: \.progress, on: self)

However, that will not work nor will the .assign(to:) operator that seems to be reserved for "re-publishing" on a SwiftUI @Published property. Why is Combine not living up to it's name here?

Jon Vogel
  • 5,244
  • 1
  • 39
  • 54
  • 2
    A Subject is itself an operator! You can subscribe it directly to the progress publisher, without sink or assign. – matt Apr 01 '21 at 22:59

2 Answers2

13

Because CurrentValueSubject is a class, you can use it as the object argument of assign(to:on:). This way, there is no memory leak:

class Download {
    
    public let progress: CurrentValueSubject<Double, Never> = .init(0)
    
    var subscriptions: [AnyCancellable] = []
    
    func start(task: URLSessionTask) {
        task.resume()
        task.progress
            .publisher(for: \.fractionCompleted)
            .assign(to: \.value, on: progress)
            .store(in: &subscriptions)
    }
    
}
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
3

You need to assign to the value of the subject, not the subject itself. It is worth noting though that assign(to: ..., on: self) leads to a memory leak ‍♀️.

func start(task: URLSessionTask) {
    task.resume()
    task.progress
        .publisher(for: \.fractionCompleted)
        .assign(to: \.progress.value, on: self)
        .store(in: &subscriptions)
}
LuLuGaGa
  • 13,089
  • 6
  • 49
  • 57
  • 1
    Thank you! This is what I was looking for. I'm aware of the memory leak in this function. I excluded that detail to keep the question narrow. I use the .assignToNoRetain function described in this thread. https://stackoverflow.com/questions/57980476/how-to-prevent-strong-reference-cycles-when-using-apples-new-combine-framework – Jon Vogel Apr 01 '21 at 17:53