Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift Combine: turn a publisher into a read-only CurrentValueSubject

Tags:

swift

combine

Sometimes my viewmodel uses a @Published property or a PassthroughSubject, but I don't want this to be writeable to the outside world. Easy enough, turn it into a public AnyPublisher and keep the writable one private, like this:

class ViewModel {
  @Published private var _models = ["hello", "world"]

  var models: AnyPublisher<[String], Never> {
    return $_models.eraseToAnyPublisher()
  }
}

let viewModel = ViewModel()
viewModel.models.sink { print($0) }

But what if you want to be able to read the value "on demand" as well? For example for this situation:

extension ViewController: UICollectionViewDelegate {
  func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    print(viewModel.models[indexPath.row])
  }
}

Obviously, the above code doesn't work.

I thought about using a CurrentValueSubject, but its value is writable too, plus I'm having a hard time turning a Publisher into a CurrentValueSubject anyway.

My current solution is to add something like this on the viewmodel:

class ViewModel {
  @Published private var _models = ["hello", "world"]
  @Published var selectedIndex: Int?

  var models: AnyPublisher<[String], Never> {
    return $_models.eraseToAnyPublisher()
  }

  var selectedModel: AnyPublisher<String, Never> {
    return models.combineLatest($selectedIndex.compactMap { $0 }).map { value, index in
      value[index]
    }.eraseToAnyPublisher()
  }
}

let viewModel = ViewModel()
viewModel.models.sink { print($0) }

viewModel.selectedModel.sink { print($0) }
viewModel.selectedIndex = 1

But it's a bit of a chore to add the selectedIndex property, the selectedModel publisher, set the selectedIndex and subscribe to the publisher.. all because I want to be able to read the current value of viewModel.models (and not have it writable).

Any better solutions?

like image 639
Kevin Renskers Avatar asked Sep 02 '25 06:09

Kevin Renskers


1 Answers

Why don't you simply keep the @Published property public, while making its setter private? That should suit your requirements perfectly while also keeping your code minimal and clean.

class ViewModel {
  @Published public private(set) var models = ["hello", "world"]
}

let viewModel = ViewModel()
viewModel.$models.sink { print($0) }
like image 198
Dávid Pásztor Avatar answered Sep 04 '25 20:09

Dávid Pásztor



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!