I try to separate network layer code with Combine, create a class HTTPAsyncManager
to hold network request. But I found the output is not triggered from dataTaskPublisher(for: url)
.
And I think the problem is on this line private var requests = Set<AnyCancellable>()
, if I move it and network code into contentView and create a State @State private var requests = Set<AnyCancellable>()
for Cancellable. It could output the data. But I do want to separate it from ContentView. What should I change?
import Combine
import SwiftUI
class HTTPAsyncManager {
private var requests = Set<AnyCancellable>()
func fetch(_ url: URL) {
let decoder = JSONDecoder()
URLSession.shared.dataTaskPublisher(for: url)
.handleEvents(receiveSubscription: { print("Receive subscription: \($0)") },
receiveOutput: { print("Receive output: \($0)") },
receiveCompletion: { print("Receive completion: \($0)") },
receiveCancel: { print("Receive cancel") },
receiveRequest: { print("Receive request: \($0)") })
.map(\.data)
.decode(type: User.self, decoder: decoder)
.replaceError(with: User.default)
.sink(receiveValue: { print($0.name) })
.store(in: &requests)
}
}
import Combine
import SwiftUI
struct User: Decodable {
var id: UUID
var name: String
static let `default` = User(id: UUID(), name: "Anonymous")
}
struct ContentView: View {
var body: some View {
VStack {
Button("Fetch user") {
let url = URL(string: "https://www.hackingwithswift.com/samples/user-24601.json")!
HTTPAsyncManager().fetch(url)
}
}
.padding()
}
}
Receive subscription: DataTaskPublisher
Receive request: unlimited
Receive cancel
The problem here is that you are not holding any reference to your HTTPAsyncManager
in your view. So it gets deallocated immediately. Your view should look like:
struct ContentView: View {
private let httpAsyncManager = HTTPAsyncManager()
var body: some View {
VStack {
Button("Fetch user") {
let url = URL(string: "https://www.hackingwithswift.com/samples/user-24601.json")!
httpAsyncManager.fetch(url)
}
}
.padding()
}
}