SwiftUI & Combine - AnyPublisher subscription at onReceive gets cancelled frequently

Environment

  • OS: macOS Monterey 12.0.1
  • Xcode: 13.1

Context & Issue

I'm implementing a feature which calls an API to fetch new data as an observed value changes. In order to avoid too many API calls, I tried throttling the observed value publisher. But I found this throttling doesn't work if I apply the .eraseToAnyPublisher() method to the publisher. After some investigation by myself, I noticed the .eraseToAnyPublisher() causes many unnecessary subscriptions and unsubscriptions to the throttled publisher. So I'm wondering if I make any mistakes and would like to get helps.

Minimal reproducible example

import SwiftUI

@main
struct SwiftUITestApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}

class ViewModel: ObservableObject {
  @Published var value: Float = 0
}

struct ContentView: View {
  @StateObject private var viewModel = ViewModel()
  @State private var text = ""
  
  var body: some View {
    VStack {
      Text(text)
      Slider(value: $viewModel.value, in: 0...1)
    }
    .padding()
    .onReceive(
      viewModel
        .$value
//        .eraseToAnyPublisher() // Uncomment this line causes the issue
        .throttle(for: 1, scheduler: DispatchQueue.main, latest: true)
        .print()
    ) { value in
      text = String(format: "%.2f", arguments: [value])
    }
  }
}

Behavior without .eraseToAnyPublisher()

If I don't apply .eraseToAnyPublisher(), this code works as expected and the .print() of the publisher will output something like:

receive subscription: (Throttle)
request unlimited
request unlimited
receive value: (0.0)
receive cancel
receive subscription: (Throttle)
request unlimited
request unlimited
receive value: (0.0)
receive value: (0.021933686)
receive value: (0.28136003)
receive value: (0.6122359)
receive value: (0.66554797)
receive value: (0.3587148)
receive value: (0.2890992)

Behavior with .eraseToAnyPublisher()

If I apply .eraseToAnyPublisher(), the throttled publisher gets subscribed and unsubscribed many times. This makes the throttling look not working. The .print() of the publisher will output something like:

receive subscription: (Throttle)
request unlimited
request unlimited
receive value: (0.0)
receive cancel
receive subscription: (Throttle)
request unlimited
request unlimited
receive value: (0.0)
receive cancel
receive subscription: (Throttle)
request unlimited
request unlimited
receive value: (0.12430311)
receive cancel
receive subscription: (Throttle)
request unlimited
request unlimited
receive value: (0.12430311)
receive cancel
receive subscription: (Throttle)
request unlimited
request unlimited
receive value: (0.13761736)
receive cancel
receive subscription: (Throttle)

Question

Though I've already found a workaround (= not using .eraseToAnyPublisher()), I would like to understand why this happens. In my understanding, .eraseToAnyPublisher() only erases the type information and should not cause any behavioral changes.

Thanks

Two years later and the issue is still there. XCode 15.0.1, iOS17.x

When using .onReceive, every type-erased publisher is cancelled and re-subscribed each time a new value arrives. Very annoying.

SwiftUI & Combine - AnyPublisher subscription at onReceive gets cancelled frequently
 
 
Q