`@Published var name: ClassType` 在 SwiftUI 的_外部_/手动触发器不起作用?
`@Published var name: ClassType` doesn't work _outside_ of SwiftUI / manual trigger?
我发现了很多与此相关的 SwiftUI 主题,但没有帮助(例如 )
这不适用于 Swift 中的 Combine(特别是 not 使用 SwiftUI):
class SomeTask {
@Published var progress = Progress(totalUnitCount: 5) // Progress is a Class
[...]
}
var task = SomeTask()
let cancellable = task.$progress.sink { print([=10=].fractionCompleted) }
task.progress.completedUnitCount = 2
这与 SwiftUI 无关,因此没有 ObservableObject
继承来获得 objectWillChange
,但即使我尝试使用 ObservableObject
和 task.objectWillChange.send()
它什么也没做,尝试添加 extension Progress: ObservableObject {}
也无济于事。
由于发布者通过 var 的 willSet
发出值,并且由于 Progress
本身就是 class-类型,因此没有任何反应。
貌似没有真正像样的手动触发方式?
我发现只有解决方案是重新分配自己,这很尴尬:
let pr = progress
progress = pr
(写progress = progress
是编译时错误)。
唯一可行的方法可能是使用 Key-value-observing/KVO and/or 编写新的 @PublishedClassType
属性 包装器?
您可以尝试使用 CurrentValueSubject<Progress, Never>
:
class SomeTask: ObservableObject {
var progress = CurrentValueSubject<Progress, Never>(Progress(totalUnitCount: 5))
func setProgress(_ value: Int) {
progress.value.completedUnitCount = value
progress.send(progress.value)
}
}
var task = SomeTask()
let cancellable = task.progress.sink { print([=11=].fractionCompleted) }
task.setProgress(3)
task.setProgress(1)
这样你的Progress
仍然可以是class。
我能够使用 KVO 实现这个,由 @propertyWrapper
包装,CurrentValueSubject
作为发布者:
@propertyWrapper
class PublishedClass<T : NSObject> {
private let subject: CurrentValueSubject<T, Never>
private var observation: NSKeyValueObservation? = nil
init<U>(wrappedValue: T, keyPath: ReferenceWritableKeyPath<T, U>) {
self.wrappedValue = wrappedValue
subject = CurrentValueSubject(wrappedValue)
observation = wrappedValue.observe(keyPath, options: [.new]) { (wrapped, change) in
self.subject.send(wrapped)
}
}
var wrappedValue: T
var projectedValue: CurrentValueSubject<T, Never> {
subject
}
deinit {
observation.invalidate()
}
}
用法:
class Bar : NSObject {
@objc dynamic var a: Int
init(a: Int) {
self.a = a
}
}
class Foo {
@PublishedClass(keyPath: \.a)
var bar = Bar(a: 0)
}
let f = Foo()
let c = f.$bar.sink(receiveValue: { x in print(x.a) })
f.bar.a = 2
f.bar.a = 3
f.bar.a = 4
输出:
0
2
3
4
使用KVO的缺点当然是你传入的key路径必须是@objc dynamic
并且keypath的root必须是NSObject
的子类。 :(
我没试过,但如果你愿意,应该可以扩展它以观察多个关键路径。
基于我的想法,我确实实现了一个 @PublishedKVO
属性 包装器并将其作为一个小的 swift 包放在 github 上,支持多个关键路径。
https://github.com/matis-schotte/PublishedKVO
可用作:
class Example {
@PublishedKVO(\.completedUnitCount)
var progress = Progress(totalUnitCount: 2)
@Published
var textualRepresentation = "text"
}
let ex = Example()
// Set up the publishers
let c1 = ex.$progress.sink { print("\([=10=].fractionCompleted) completed") }
let c1 = ex.$textualRepresentation.sink { print("\([=10=])") }
// Interact with the class as usual
ex.progress.completedUnitCount += 1
// outputs "0.5 completed"
// And compare with Combines @Published (almost°) same behaviour
ex.textualRepresentation = "string"
// outputs "string"
ex.$progress.emit() // Re-emits the current value
ex.$progress.send(ex.progress) // Emits given value
我发现了很多与此相关的 SwiftUI 主题,但没有帮助(例如
这不适用于 Swift 中的 Combine(特别是 not 使用 SwiftUI):
class SomeTask {
@Published var progress = Progress(totalUnitCount: 5) // Progress is a Class
[...]
}
var task = SomeTask()
let cancellable = task.$progress.sink { print([=10=].fractionCompleted) }
task.progress.completedUnitCount = 2
这与 SwiftUI 无关,因此没有 ObservableObject
继承来获得 objectWillChange
,但即使我尝试使用 ObservableObject
和 task.objectWillChange.send()
它什么也没做,尝试添加 extension Progress: ObservableObject {}
也无济于事。
由于发布者通过 var 的 willSet
发出值,并且由于 Progress
本身就是 class-类型,因此没有任何反应。
貌似没有真正像样的手动触发方式?
我发现只有解决方案是重新分配自己,这很尴尬:
let pr = progress
progress = pr
(写progress = progress
是编译时错误)。
唯一可行的方法可能是使用 Key-value-observing/KVO and/or 编写新的 @PublishedClassType
属性 包装器?
您可以尝试使用 CurrentValueSubject<Progress, Never>
:
class SomeTask: ObservableObject {
var progress = CurrentValueSubject<Progress, Never>(Progress(totalUnitCount: 5))
func setProgress(_ value: Int) {
progress.value.completedUnitCount = value
progress.send(progress.value)
}
}
var task = SomeTask()
let cancellable = task.progress.sink { print([=11=].fractionCompleted) }
task.setProgress(3)
task.setProgress(1)
这样你的Progress
仍然可以是class。
我能够使用 KVO 实现这个,由 @propertyWrapper
包装,CurrentValueSubject
作为发布者:
@propertyWrapper
class PublishedClass<T : NSObject> {
private let subject: CurrentValueSubject<T, Never>
private var observation: NSKeyValueObservation? = nil
init<U>(wrappedValue: T, keyPath: ReferenceWritableKeyPath<T, U>) {
self.wrappedValue = wrappedValue
subject = CurrentValueSubject(wrappedValue)
observation = wrappedValue.observe(keyPath, options: [.new]) { (wrapped, change) in
self.subject.send(wrapped)
}
}
var wrappedValue: T
var projectedValue: CurrentValueSubject<T, Never> {
subject
}
deinit {
observation.invalidate()
}
}
用法:
class Bar : NSObject {
@objc dynamic var a: Int
init(a: Int) {
self.a = a
}
}
class Foo {
@PublishedClass(keyPath: \.a)
var bar = Bar(a: 0)
}
let f = Foo()
let c = f.$bar.sink(receiveValue: { x in print(x.a) })
f.bar.a = 2
f.bar.a = 3
f.bar.a = 4
输出:
0
2
3
4
使用KVO的缺点当然是你传入的key路径必须是@objc dynamic
并且keypath的root必须是NSObject
的子类。 :(
我没试过,但如果你愿意,应该可以扩展它以观察多个关键路径。
基于我的想法,我确实实现了一个 @PublishedKVO
属性 包装器并将其作为一个小的 swift 包放在 github 上,支持多个关键路径。
https://github.com/matis-schotte/PublishedKVO
可用作:
class Example {
@PublishedKVO(\.completedUnitCount)
var progress = Progress(totalUnitCount: 2)
@Published
var textualRepresentation = "text"
}
let ex = Example()
// Set up the publishers
let c1 = ex.$progress.sink { print("\([=10=].fractionCompleted) completed") }
let c1 = ex.$textualRepresentation.sink { print("\([=10=])") }
// Interact with the class as usual
ex.progress.completedUnitCount += 1
// outputs "0.5 completed"
// And compare with Combines @Published (almost°) same behaviour
ex.textualRepresentation = "string"
// outputs "string"
ex.$progress.emit() // Re-emits the current value
ex.$progress.send(ex.progress) // Emits given value