如何解决更新商店的对象时,在ViewModel ObservableObjects中自动触发objectWillChange.send
对于使用Combine和SwiftUI的Store / Factory / ViewModel模式,我希望符合Store协议的类在指定的模型对象更改内部属性时公开发布者。然后,任何订阅的ViewModel都可以触发objectWillChange来显示更改。
(这是必要的,因为更改会在通过引用传递的模型对象中被忽略,因此对于工厂通过的商店拥有的模型,@ Published / ObservableObject不会自动触发。它可以正常工作在存储区和VM中调用objectWillChange,但是没有任何被动监听的VM。)
那是一个委托模式,对吗,将@ Published / ObservableObject扩展为通过引用传递的对象?通过合并博客,书籍和文档来进行梳理还没有触发可能是很标准的事情的想法。
粗暴的尝试
我认为,如果我在外部公开VM的objectWillChange,PassthroughSubject
在Ext+VM republishChanges(of myStore: Store)
上安装限制器(例如,节流阀,removeDuplicates)似乎没有限制.sink调用,也没有找到一种明显的方法来重置PassthroughSubject和VM的接收器之间的需求。 。或了解如何将订户附加到符合协议的PassthroughSubject。有什么建议吗?
商店侧
struct Library {
var books: // some dictionary
}
class LocalLibraryStore: LibraryStore {
private(set) var library: Library {
didSet { publish() }
}
var changed = PassthroughSubject<Any,Never>()
func removeBook() {}
}
protocol LibraryStore: Store {
var changed: PassthroughSubject<Any,Never> { get }
var library: Library { get }
}
protocol Store {
var changed: PassthroughSubject<Any,Never> { get }
}
extension Store {
func publish() {
changed.send(1)
print("This will fire once.")
}
}
VM端
class BadgeVM: VM {
init(store: LibraryStore) {
self.specificStore = store
republishChanges(of: jokesStore)
}
var objectWillChange = ObservableObjectPublisher() // Exposed {set} for external call
internal var subscriptions = Set<AnyCancellable>()
@Published private var specificStore: LibraryStore
var totalBooks: Int { specificStore.library.books.keys.count }
}
protocol VM: ObservableObject {
var subscriptions: Set<AnyCancellable> { get set }
var objectWillChange: ObservableObjectPublisher { get set }
}
extension VM {
internal func republishChanges(of myStore: Store) {
myStore.changed
// .throttle() doesn't silence as hoped
.sink { [unowned self] _ in
print("Executed for each object inside the Store's published object.")
self.objectWillChange.send()
}
.store(in: &subscriptions)
}
}
class OtherVM: VM {
init(store: LibraryStore) {
self.specificStore = store
republishChanges(of: store)
}
var objectWillChange = ObservableObjectPublisher() // Exposed {set} for external call
internal var subscriptions = Set<AnyCancellable>()
@Published private var specificStore: LibraryStore
var isBookVeryExpensive: Bool { ... }
func bookMysteriouslyDisappears() {
specificStore.removeBook()
}
}
解决方法
似乎您想要的是一种在其内部属性更改时通知的类型。听起来很像ObservableObject
所做的事情。
因此,使您的Store
协议继承自ObservableObject
:
protocol Store: ObservableObject {}
然后,符合Store
的类型可以决定要通知的属性,例如,使用@Published
:
class StringStore: Store {
@Published var text: String = ""
}
第二,您希望视图模型在商店通知他们的objectWillChange
发布者时自动将其解雇。
自动部分可以通过基类而不是协议来完成,因为它需要存储预订。如果需要,您可以保留协议要求:
protocol VM {
associatedtype S: Store
var store: S { get }
}
class BaseVM<S: Store>: ObservableObject,VM {
var c : AnyCancellable? = nil
let store: S
init(store: S) {
self.store = store
c = self.store.objectWillChange.sink { [weak self] _ in
self?.objectWillChange.send()
}
}
}
class MainVM: BaseVM<StringStore> {
// ...
}
下面是如何使用它的示例:
let stringStore = StringStore();
let mainVm = MainVM(store: stringStore)
// this is conceptually what @ObservedObject does
let c = mainVm.objectWillChange.sink {
print("change!") // this will fire after next line
}
stringStore.text = "new text"
,
感谢@NewDev指出子类化是更智能的路线。
如果您想嵌套ObservableObjects或让ObservableObject重新发布传递给它的对象中对象的更改,则此方法比我的问题用更少的代码。
在使用属性包装器进行进一步简化(以获取父objectWillChange并进行进一步简化)时,我注意到此线程中有一种类似的方法:https://stackoverflow.com/a/58406402/11420986。这只是在使用可变参数上有所不同。
定义VM和存储/存储库类
import Foundation
import Combine
class Repo: ObservableObject {
func publish() {
objectWillChange.send()
}
}
class VM: ObservableObject {
private var repoSubscriptions = Set<AnyCancellable>()
init(subscribe repos: Repo...) {
repos.forEach { repo in
repo.objectWillChange
.receive(on: DispatchQueue.main) // Optional
.sink(receiveValue: { [weak self] _ in
self?.objectWillChange.send()
})
.store(in: &repoSubscriptions)
}
}
}
示例实现
- 回购:将didSet {publish()}添加到模型对象中
- VM:super.init()接受任意数量的存储库以重新发布
import Foundation
class UserDirectoriesRepo: Repo,DirectoriesRepository {
init(persistence: Persistence) {
self.userDirs = persistence.loadDirectories()
self.persistence = persistence
super.init()
restoreBookmarksAccess()
}
private var userDirs: UserDirectories {
didSet { publish() }
}
var someExposedSliceOfTheModel: [RootDirectory] {
userDirs.rootDirectories.filter { $0.restoredURL != nil }
}
...
}
import Foundation
class FileStructureVM: VM {
init(directoriesRepo: DirectoriesRepository) {
self.repo = directoriesRepo
super.init(subscribe: directoriesRepo)
}
@Published // No longer necessary
private var repo: DirectoriesRepository
var rootDirectories: [RootDirectory] {
repo.rootDirectories.sorted ...
}
...
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。