Combine: Is it correct behavior when MergeMany duplicates entries and fire several events instead of one?

Consider that you have following implementation:

Plot

extension EntryViewModel {
    enum UserAction {
      case one, two, three
    }
}

class EntryViewModel {
  private var userActionSubject: PassthroughSubject<UserAction, Never> = .init()
  public var userActionPublisher: AnyPublisher<UserAction, Never> = .empty() // It doesn't matter, It will be set somewhere in setup.
  init() {
    
  }
  func setup() {
    self.userActionPublisher = self.userActionSubject.eraseToAnyPublisher()
  }
}

And you have a list of these entries.

class ListViewModel {
    @Published var entries: [EntryViewModel] = []
}

You would like to build a Handler which will handle these UserAction.

class Handler {
    var subscription: AnyCancellable?

    func configured(stream: AnyPublisher<UserAction, Never>) {
        self.subscription = stream.sink{[weak self] self?.process($0)}
    }
}

List view model could call updates on this entries list as remove or add via common =

private func update(entries: [EntryViewModel]) {
    let difference = entries.difference(from: self.entries) { $0.id == $1.id }
    if let result = self.entries.applying(difference) {
        self.entries = result
    }
    else {
        // We should set all entries, because our collection is empty?
        self.entries = entries
    }
}

The most difficult part is Merging all entries events into one stream.

func stream(from viewModel: ListViewModel) -> AnyPublisher<UserAction, Never> {
    viewModel.$entries.flatMap { entries in
        Publishers.MergeMany({entries.map(\.userActionSubject)})
    }.eraseToAnyPublisher()
}

Question

When I add or remove entry from entries in ListViewModel by applying difference and call setter, I catch interesting behavior.

Entry send several updates to handler.
I assume that it is happening, because stream capture several copies (middle copies ) of this entries array.

At the start I have:

[A, B, C] entries.

I merge them into:

[A, B, C].map({\.publisher}).merge

Next, updates do the following:

D entry is coming.
[A, B, C, D] array of entries is correctly computed and setter is ok.

But! On Publisher side it will be

[A, B, C].map({\.publisher}) + [A, B, C, D].map({\.publisher}).

Terms of Service

Privacy Policy

Cookie Policy