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)
这个方案的好处是保留了值类型的安全性,而且本地状态更新是同步的,用户点击后立刻能看到已读效果,不用等网络请求返回,体验更好。
额外注意点
- 不管用哪种方案,都要确保你的表格视图是绑定到
notificationsDriver的,这样数据源更新时才能自动刷新。 - 如果用方案2,更新数组时一定要创建新数组(而不是修改原数组),因为struct是值类型,只有新数组才能触发Relay发送新的信号。
内容的提问来源于stack exchange,提问作者Oleksandr Balabanov
相关产品推荐
相关产品推荐

