[英]SwiftUI Why Can't pass a publisher between Views?
我只是想做一些这样的测试↓
代码是↓(第一次查看)
struct ContentView: View {
let publisher = URLSession(configuration: URLSessionConfiguration.default)
.dataTaskPublisher(for: URLRequest(url: URL(string: "https://v.juhe.cn/joke/content/list.php?sort=asc&page=&pagesize=&time=1418816972&key=aa73ebdd8672a2b9adc9dbb2923184c8")!))
.map(\.data.description)
.replaceError(with: "Error!")
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
var body: some View {
NavigationView {
List {
NavigationLink(destination: ResponseView(publisher: publisher)) {
Text("Hello, World!")
}
}.navigationBarTitle("Title", displayMode: .inline)
}
}
}
(第二视图)
struct ResponseView: View {
let publisher: AnyPublisher<String, Never>
@State var content: String = ""
var body: some View {
HStack {
VStack {
Text(content)
.font(.system(size: 12))
.onAppear { _ = self.publisher.assign(to: \.content, on: self) }
Spacer()
}
Spacer()
}
}
}
但是代码不起作用。 请求失败,消息失败↓
2020-11-11 11:08:04.657375+0800 PandaServiceDemo[83721:1275181] Task <6B53516E-5127-4C5E-AD5F-893F1AEE77E8>.<1> finished with error [-999] Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLStringKey=https://v.juhe.cn/joke/content/list.php?sort=asc&page=&pagesize=&time=1418816972&key=aa73ebdd8672a2b9adc9dbb2923184c8, NSLocalizedDescription=cancelled, NSErrorFailingURLKey=https://v.juhe.cn/joke/content/list.php?sort=asc&page=&pagesize=&time=1418816972&key=aa73ebdd8672a2b9adc9dbb2923184c8}
有人可以告诉我发生了什么以及这样做的正确方法是什么?
您需要存储订阅,否则会被取消初始化并自动取消。
通常,这是这样做的:
var cancellables: Set<AnyCancellable> = []
// ...
publisher
.sink {...}
.store(in: &cancellables)
所以,你可以像上面那样创建一个@State
属性,或者你可以使用.onReceive
:
let publisher: AnyPublisher<String, Never>
var body: some View {
HStack {
// ...
}
.onReceive(publisher) {
content = $0
}
}
你应该小心上面的方法,因为如果ResponseView
被重新初始化,它会得到一个发布者的副本(大多数发布者是值类型),所以它会开始一个新的请求。
为避免这种情况,请将.share()
添加到发布者:
let publisher = URLSession(configuration: URLSessionConfiguration.default)
.dataTaskPublisher(...)
//...
.share()
.eraseToAnyPublisher()
这里的问题是,订阅没有存储在任何地方。 您必须将其存储在AnyCancellable
并保留订阅。
每当您调试组合相关问题时,请使用.print()
运算符。 我觉得它真的很有用。
正确的做法是将发布者和订阅者提取到一个ObservableObject
并将其注入到View
或使用@StateObject
class DataProvider: ObservableObject {
@Published var content: String = ""
private var bag = Set<AnyCancellable>()
private let publisher: AnyPublisher<String, Never>
init() {
publisher = URLSession(configuration: URLSessionConfiguration.default)
.dataTaskPublisher(for: URLRequest(url: URL(string: "https://v.juhe.cn/joke/content/list.php?sort=asc&page=&pagesize=&time=1418816972&key=aa73ebdd8672a2b9adc9dbb2923184c8")!))
.map(\.data.description)
.print()
.replaceError(with: "Error!")
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
func loadData() {
publisher.assign(to: \.content, on: self).store(in: &bag)
}
}
struct ContentView: View {
@StateObject var dataProvider = DataProvider()
var body: some View {
NavigationView {
List {
NavigationLink(destination: ResponseView(dataProvider: dataProvider)) {
Text("Hello, World!")
}
}.navigationBarTitle("Title", displayMode: .inline)
}
}
}
struct ResponseView: View {
let dataProvider: DataProvider
var body: some View {
HStack {
VStack {
Text(dataProvider.content)
.font(.system(size: 12))
.onAppear {
self.dataProvider.loadData()
}
Spacer()
}
Spacer()
}
}
}
请注意,我们使用@StateObject
来确保DataProvider
实例不会在视图更新时被破坏。
就 SwiftUI 而言,您在做一些根本错误的事情:从视图创建发布者。 这意味着每次实例化ContentView
都会创建一个新的发布者,并且出于所有方式和目的,这可能会发生很多次,SwiftUI 不保证 View 只会实例化一次。
您需要做的是将发布的内容提取到某个对象中,该对象可以从上游注入,也可以通过@StateObject
由 SwiftUI 管理。
好吧,这项工作有两种方法:一种方法更好
方式一:
import SwiftUI
struct ContentView: View {
@State var urlForPublicsh: URL?
var body: some View {
VStack
{
Text(urlForPublicsh?.absoluteString ?? "nil")
.padding()
Button("Change the Publisher") {urlForPublicsh = URL(string: "https://stackoverflow.com")}
.padding()
SecondView(urlForPublicsh: $urlForPublicsh)
}
.onAppear()
{
urlForPublicsh = URL(string: "https://www.apple.com")
}
}
}
struct SecondView: View {
@Binding var urlForPublicsh: URL?
var body: some View {
Text(urlForPublicsh?.absoluteString ?? "nil")
.padding()
}
}
方式二:
import SwiftUI
class UrlForPublicshModel: ObservableObject
{
static let shared = UrlForPublicshModel()
@Published var url: URL?
}
struct ContentView: View {
@StateObject var urlForPublicshModel = UrlForPublicshModel.shared
var body: some View {
VStack
{
Text(urlForPublicshModel.url?.absoluteString ?? "nil")
.padding()
Button("Change the Publisher") {urlForPublicshModel.url = URL(string: "https://stackoverflow.com")}
.padding()
SecondView()
}
.onAppear()
{
urlForPublicshModel.url = URL(string: "https://www.apple.com")
}
}
}
struct SecondView: View {
@ObservedObject var urlForPublicshModel = UrlForPublicshModel.shared
var body: some View {
Text(urlForPublicshModel.url?.absoluteString ?? "nil")
.padding()
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.