Swift Combine:将发布者转变为只读 CurrentValueSubject

Kev*_*ers 4 swift combine

有时我的视图模型使用@Published属性或 a PassthroughSubject,但我不希望它对外界可写。很简单,将其变成公共的AnyPublisher并将可写的保持为私有,如下所示:

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

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

let viewModel = ViewModel()
viewModel.models.sink { print($0) }
Run Code Online (Sandbox Code Playgroud)

但是,如果您也希望能够“按需”读取该值怎么办?例如对于这种情况:

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

Run Code Online (Sandbox Code Playgroud)

显然,上面的代码是行不通的。

我考虑过使用 a CurrentValueSubject,但它的值也是可写的,而且无论如何我都很难将 Publisher 变成 CurrentValueSubject 。

我当前的解决方案是在视图模型上添加类似的内容:

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
Run Code Online (Sandbox Code Playgroud)

selectedIndex但是添加属性、发布者、设置 selectedIndex 并订阅发布者有点麻烦selectedModel。所有这些都是因为我希望能够读取当前值viewModel.models(并且不能使其可写)。

还有更好的解决方案吗?

Dáv*_*tor 7

为什么不简单地将@Published属性保持为公共,同时将其 setter 设为私有呢?这应该完全满足您的要求,同时保持您的代码最少和干净。

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

let viewModel = ViewModel()
viewModel.$models.sink { print($0) }
Run Code Online (Sandbox Code Playgroud)

  • 这里的问题是,如果是“@Published”,则无法在“protocol”中定义“var”,因此无法实现抽象,除非我误解了。 (2认同)