假设我有以下内容ObservableObject:
import SwiftUI
class SomeObservable: ObservableObject {
@Published var information: String = ""
init() {
Timer.scheduledTimer(
timeInterval: 1.0,
target: self,
selector: #selector(updateInformation),
userInfo: nil,
repeats: true
).fire()
}
@objc func updateInformation() {
information = String("RANDOM_INFO".shuffled().prefix(5))
}
}
Run Code Online (Sandbox Code Playgroud)
还有一个View,它观察到:
struct SomeView: View {
@ObservedObject var observable: SomeObservable
var body: some View {
Text(observable.information)
}
}
Run Code Online (Sandbox Code Playgroud)
以上将按预期工作。
该View重绘自己的时候ObservableObject变化:
How could I do the same (say calling a function) in a "pure" struct that also observes the same ObservableObject? By "pure" I mean something that does not conform to View:
struct SomeStruct {
@ObservedObject var observable: SomeObservable
// How to call this function when "observable" changes?
func doSomethingWhenObservableChanges() {
print("Triggered!")
}
}
Run Code Online (Sandbox Code Playgroud)
(It could also be a class, as long as it's able to react to the changes on the observable.)
It seems to be conceptually very easy, but I'm clearly missing something.
(Note: I'm using Xcode 11, beta 6.)
Here is a possible solution, based on the awesome answer provided by @Fabian:
import SwiftUI
import Combine
class SomeObservable: ObservableObject {
@Published var information: String = "" // Will be automagically consumed by `Views`.
let updatePublisher = PassthroughSubject<Void, Never>() // Can be consumed by other classes / objects.
// Added here only to test the whole thing.
var someObserverClass: SomeObserverClass?
init() {
// Randomly change the information each second.
Timer.scheduledTimer(
timeInterval: 1.0,
target: self,
selector: #selector(updateInformation),
userInfo: nil,
repeats: true
).fire() }
@objc func updateInformation() {
// For testing purposes only.
if someObserverClass == nil { someObserverClass = SomeObserverClass(observable: self) }
// `Views` will detect this right away.
information = String("RANDOM_INFO".shuffled().prefix(5))
// "Manually" sending updates, so other classes / objects can be notified.
updatePublisher.send()
}
}
struct SomeObserverView: View {
@ObservedObject var observable: SomeObservable
var body: some View {
Text(observable.information)
}
}
class SomeObserverClass {
@ObservedObject var observable: SomeObservable
// More on AnyCancellable on: apple-reference-documentation://hs-NDfw7su
var cancellable: AnyCancellable?
init(observable: SomeObservable) {
self.observable = observable
// `sink`: Attaches a subscriber with closure-based behavior.
cancellable = observable.updatePublisher.sink(receiveValue: { [weak self] _ in
guard let self = self else { return }
self.doSomethingWhenObservableChanges()
})
}
func doSomethingWhenObservableChanges() {
print(observable.information)
}
}
Run Code Online (Sandbox Code Playgroud)
Result
(Note: it's necessary to run the app in order to check the console output.)
Fab*_*ian 20
旧方法是使用您注册的回调。较新的方法是使用Combine框架创建发布者,您可以为其注册进一步的处理,或者在这种情况下sink,每次source publisher发送消息时都会调用a 。这里的发布者不发送任何内容,因此类型为<Void, Never>。
要从计时器中获取发布者,可以直接通过Combine或通过创建通用发布者PassthroughSubject<Void, Never>()、注册消息并在timer-callbackvia 中发送它们来完成publisher.send()。该示例有两种变体。
每个ObservableObject确实有一个.objectWillChange可以为它注册一个出版商sink为你做同样的Timer publishers。每次调用它或每次@Published变量更改时都应该调用它。但是请注意,这是在更改之前而不是在更改之后调用的。(DispatchQueue.main.async{}更改完成后在接收器内部进行反应)。
每个接收器调用都会创建一个AnyCancellable必须存储的对象,通常在sink应该具有相同生命周期的对象中。一旦可取消对象被解构(或.cancel()在其上被调用),sink就不会再次被调用。
import SwiftUI
import Combine
struct ReceiveOutsideView: View {
#if swift(>=5.3)
@StateObject var observable: SomeObservable = SomeObservable()
#else
@ObservedObject var observable: SomeObservable = SomeObservable()
#endif
var body: some View {
Text(observable.information)
.onReceive(observable.publisher) {
print("Updated from Timer.publish")
}
.onReceive(observable.updatePublisher) {
print("Updated from updateInformation()")
}
}
}
class SomeObservable: ObservableObject {
@Published var information: String = ""
var publisher: AnyPublisher<Void, Never>! = nil
init() {
publisher = Timer.publish(every: 1.0, on: RunLoop.main, in: .common).autoconnect().map{_ in
print("Updating information")
//self.information = String("RANDOM_INFO".shuffled().prefix(5))
}.eraseToAnyPublisher()
Timer.scheduledTimer(
timeInterval: 1.0,
target: self,
selector: #selector(updateInformation),
userInfo: nil,
repeats: true
).fire()
}
let updatePublisher = PassthroughSubject<Void, Never>()
@objc func updateInformation() {
information = String("RANDOM_INFO".shuffled().prefix(5))
updatePublisher.send()
}
}
class SomeClass {
@ObservedObject var observable: SomeObservable
var cancellable: AnyCancellable?
init(observable: SomeObservable) {
self.observable = observable
cancellable = observable.publisher.sink{ [weak self] in
guard let self = self else {
return
}
self.doSomethingWhenObservableChanges() // Must be a class to access self here.
}
}
// How to call this function when "observable" changes?
func doSomethingWhenObservableChanges() {
print("Triggered!")
}
}
Run Code Online (Sandbox Code Playgroud)
这里要注意,如果管道末端没有注册接收器或接收器,则该值将丢失。例如,创建PassthroughSubject<T, Never>,立即发送一个值,然后返回发布者会使发送的消息丢失,尽管您随后在该主题上注册了接收器。通常的解决方法是将主题创建和消息发送包装在一个Deferred {}块中,一旦接收器注册,它只会在其中创建所有内容。
一个评论者注意到它ReceiveOutsideView.observable是由 拥有的ReceiveOutsideView,因为 observable 是在内部创建并直接赋值的。重新初始化时observable将创建一个新实例。在这种情况下,可以通过使用@StateObject代替来防止@ObservableObject这种情况。
| 归档时间: |
|
| 查看次数: |
562 次 |
| 最近记录: |