You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RxSwift中Driver值修改问题:点击未读通知后无法变更模型状态

这个问题我之前做通知中心功能时也踩过坑!核心原因大概率是你的Notification是值类型(比如Swift struct),而且当前拿到的是常量实例,所以直接修改isRead肯定会报错。下面给你几个可行的方案,你可以根据自己的代码结构来选:

方案1:把Notification改成引用类型(Class)

如果你的业务场景允许,直接把Notification从struct改成class就行。因为class是引用类型,即使你持有一个常量引用,也能修改它的内部属性——你修改的是引用指向的实例内容,而不是引用本身。

修改后的模型大概是这样:

class Notification {
    let notificationID: String
    var isRead: Bool
    // 其他属性...
    
    init(notificationID: String, isRead: Bool) {
        self.notificationID = notificationID
        self.isRead = isRead
    }
}

这样你原来的代码里notification.isRead = true就能正常工作了,而且修改后会直接反映到原数组的实例上(因为是引用),只要你的notifications Driver是基于这个数组的,表格视图就能自动刷新。

方案2:创建可变副本(保留Struct的情况下)

如果因为业务原因必须用struct(比如要利用值类型的不可变性、线程安全等特性),那你需要先创建一个可变的副本,修改属性后再更新本地数据源:

首先,你的ViewModel里不能只暴露notifications: Driver<[Notification]>,得有一个可变的状态容器,比如BehaviorRelay<[Notification]>,然后notifications Driver从这个Relay转换而来:

class NotificationViewModel {
    private let notificationsRelay = BehaviorRelay<[Notification]>(value: [])
    var notifications: Driver<[Notification]> {
        notificationsRelay.asDriver()
    }
    // 其他输入输出流...
}

然后修改你的点击处理代码:

touchSingleNotificationIntent
    .filter { !$0.isRead }
    .map { originalNotification in
        // 创建可变副本,修改isRead状态
        var updatedNotification = originalNotification
        updatedNotification.isRead = true
        return updatedNotification
    }
    .do(onNext: { updatedNotification in
        // 更新本地数据源:替换原数组中的对应通知
        var currentNotifications = self.notificationsRelay.value
        if let index = currentNotifications.firstIndex(where: { $0.notificationID == updatedNotification.notificationID }) {
            currentNotifications[index] = updatedNotification
            self.notificationsRelay.accept(currentNotifications)
        }
        // 这一步之后,notifications Driver会发送新的数组,表格视图自动刷新
    })
    .map { $0.notificationID }
    .flatMap(self.markNotificationAsRead)
    .subscribe(onError: { [weak self] error in
        // 可选:如果网络请求失败,把本地状态改回去
        guard let self = self else { return }
        var currentNotifications = self.notificationsRelay.value
        if let index = currentNotifications.firstIndex(where: { $0.notificationID == $0.notificationID }) {
            var rolledBackNotification = currentNotifications[index]
            rolledBackNotification.isRead = false
            currentNotifications[index] = rolledBackNotification
            self.notificationsRelay.accept(currentNotifications)
        }
    })
    .disposed(by: bag)

这个方案的好处是保留了值类型的安全性,而且本地状态更新是同步的,用户点击后立刻能看到已读效果,不用等网络请求返回,体验更好。

额外注意点

  • 不管用哪种方案,都要确保你的表格视图是绑定到notifications Driver的,这样数据源更新时才能自动刷新。
  • 如果用方案2,更新数组时一定要创建新数组(而不是修改原数组),因为struct是值类型,只有新数组才能触发Relay发送新的信号。

内容的提问来源于stack exchange,提问作者Oleksandr Balabanov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:36:01