onReceive如何知道不再对发布者重新订阅?
如果我有一个类似这样的视图
struct SomeView: View {
@State var counter = 0
static let subject = CurrentValueSubject<String, Never>(
"hello world"
).handleEvents(receiveSubscription: { newSub in
print("newSub: \(newSub)")
})
var body: some View {
VStack {
Text("\(counter)")
Button {
counter += 1
} label: {
Text("increment counter")
}
}.onReceive(Self.subject) { newValue in
print(newValue)
}
}
}
每当我增加计数器时,似乎不会导致对发布者进行重新订阅,这让我感到惊讶,因为该发布者没有实现Equatable协议。那么SwiftUI在重新计算body以知道不需要重新订阅时,怎么能知道发布者没有改变呢?(据我所知,SwiftUI会在更新视图之前,用更新后的值来比较视图的值以判断是否有变化,但这只有在视图的值实现了Equatable时才会起作用,对吧?)
对于onReceive的期望行为,据我理解,是因为发布者不能被比较,SwiftUI无法判断这是一个新的还是同一个发布者,因此会再次调用onReceive。
对我来说,onReceive是 SwiftUI中最怪异的部分,我还没有找到一段WWDC视频解释发布者如何与SwiftUI生命周期配合,以防止重复的取消订阅/重新订阅。
解决方案
发布者不会因为同样的原因被多次订阅,比如 .onAppear { ... } 或 .task { ... } 中的代码不会多次运行。
视图是有身份标识的(参见 Demystifying SwiftUI)。只要视图的身份保持不变,SwiftUI就知道它仍然是同一个视图,不会再次执行传给 onAppear/task 的闭包。同样地,SwiftUI也不会再次订阅传给 onReceive 的发布者。
你可以通过仅使用 onAppear 和 onDisappear,编写一个类似于 onReceive 的视图修饰符。
struct OnReceiveModifier<P: Publisher>: ViewModifier where P.Failure == Never {
let publisher: P
let perform: (P.Output) -> Void
@State private var cancellable: (any Cancellable)?
func body(content: Content) -> some View {
content
.onAppear {
cancellable = publisher.sink(receiveValue: perform)
}
.onDisappear {
cancellable?.cancel()
}
}
}
.modifier(OnReceiveModifier(publisher: Self.subject) {
print($0)
})
这只是一个用来给你一个粗略心理模型的示意性例子。当然,实际的 onReceive 并不是这样实现的。首先,onReceive 会在下一次视图更新时重新订阅该发布者,如果现有订阅者在收到完成信号时。
SwiftUI.swiftinterface显示,onReceive 返回一个名为 SubscriptionView 的视图,它把 Never 作为自己的 Body。这意味着它是通过内部的AttributeGraph框架实现的,实现诸如 _makeView、_makeViewList 等方法,而不是 body。它很可能使用自定义的 Subscriber 实现,而不是 sink。