You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Combine中dataTaskPublisher(for:)无输出,如何分离网络层代码?

问题

使用Combine分离网络层代码,创建HTTPAsyncManager类处理网络请求时,发现dataTaskPublisher(for: url)未触发输出。将private var requests = Set<AnyCancellable>()移到ContentView中并声明为@State private var requests = Set<AnyCancellable>()后能正常输出数据,但希望保持网络层与ContentView分离,询问修改方案。

相关代码

HTTPAsyncManager 实现

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

解决方案

问题根源在于:每次点击按钮时创建的HTTPAsyncManager是局部变量,方法执行完毕后该实例会被立即销毁,其内部的requests集合也随之释放,导致Combine订阅被取消,网络请求尚未完成就被终止。

只需让ContentView持有HTTPAsyncManager的实例,避免其被提前销毁即可,修改后的代码如下:

修改后的 ContentView

import Combine
import SwiftUI

struct ContentView: View {
    // 使用@StateObject持有HTTPAsyncManager实例,保证其生命周期与视图一致
    @StateObject private var httpManager = HTTPAsyncManager()
    
    var body: some View {
        VStack {
            Button("Fetch user") {
                let url = URL(string: "https://www.hackingwithswift.com/samples/user-24601.json")!
                httpManager.fetch(url)
            }
        }
        .padding()
    }
}

HTTPAsyncManager 保持不变

原HTTPAsyncManager的代码无需修改,现在实例被视图持有,requests集合不会被提前释放,订阅可以正常完成,网络请求的输出也能正常触发。

内容的提问来源于stack exchange,提问作者Zhou Haibo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 19:45:26