You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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需要的计算属性

你提到的messageToDisplayshouldShowHeart这类驱动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

火山引擎 最新活动