RxSwift新手求教:MVVM架构下订阅数据模型属性变化的正确方式
正确订阅RxSwift MVVM中模型属性变化的方式
Hey there! 作为刚接触RxSwift的开发者,你这个疑问特别典型——很多人刚开始都会纠结要不要把模型里所有属性都改成Rx的可观察对象,其实完全没必要,咱们换个更合理的思路来处理:
1. 用BehaviorRelay持有整个Post模型
首先,不要把Post的每个属性都改成Rx类型,而是在ViewModel里用BehaviorRelay(RxSwift推荐替代已弃用的Variable的类型)持有整个Post实例。它的作用是保存当前的Post值,并且在值更新时自动通知所有订阅者。
示例代码:
class PostViewModel { // 初始化一个初始的Post实例 private let initialPost = Post(title: "", content: "", likeCount: 0, isLiked: false) // 用BehaviorRelay持有Post,外部只能通过accept更新,通过asObservable订阅 private let postRelay = BehaviorRelay<Post>(value: initialPost) func startUpdate() { // 模拟持续更新Post的逻辑,比如从网络轮询或者定时器更新 Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] _ in guard let self = self else { return } var newPost = self.postRelay.value newPost.likeCount += 10 // 更新Relay的值,触发订阅者的回调 self.postRelay.accept(newPost) } } }
2. 从Relay派生UI需要的计算属性
你提到的messageToDisplay和shouldShowHeart这类驱动UI的属性,不需要改成Variable,而是通过map操作符从postRelay派生只读的Observable序列,这样UI可以直接订阅这些序列:
extension PostViewModel { // 派生显示文本的Observable var messageToDisplay: Observable<String> { postRelay.map { post in // 这里写你原来计算属性的逻辑,比如拼接标题和点赞数 "\(post.title)\n当前点赞:\(post.likeCount)" } } // 派生是否显示爱心按钮的Observable var shouldShowHeart: Observable<Bool> { postRelay.map { post in // 这里写判断逻辑,比如点赞数超过100显示爱心 post.likeCount > 100 && !post.isLiked } } }
3. 保持Post模型的纯净性
Post模型本身应该是普通的Swift结构体(推荐用值类型,因为值语义更适合Rx的变化检测),不需要混入任何Rx相关类型,只负责存储数据:
struct Post { var title: String var content: String var likeCount: Int var isLiked: Bool // 其他业务属性... }
4. UI层的绑定与订阅
在ViewController中,你只需要把ViewModel的这些Observable序列绑定到对应的UI控件即可,记得用disposeBag管理订阅生命周期:
class PostViewController: UIViewController { @IBOutlet weak var postLabel: UILabel! @IBOutlet weak var heartButton: UIButton! private let viewModel = PostViewModel() private let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() bindViewModel() viewModel.startUpdate() } private func bindViewModel() { // 绑定显示文本到Label viewModel.messageToDisplay .bind(to: postLabel.rx.text) .disposed(by: disposeBag) // 绑定爱心按钮的显示状态 viewModel.shouldShowHeart .bind(to: heartButton.rx.isHidden) .disposed(by: disposeBag) } }
额外小贴士
- 如果你的Post是类(引用类型),修改属性后要记得调用
postRelay.accept(post),因为Relay只会在收到新的实例引用时发出事件;如果是结构体(值类型),修改属性会生成新的实例,直接accept新实例即可。 - 尽量避免在模型层引入Rx类型,这样模型可以在非Rx场景下复用,也符合单一职责原则。
内容的提问来源于stack exchange,提问作者Jack Guo




