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

SwiftUI MVVM:如何让ViewModel感知Service类代理完成事件?

让ViewModel直接感知NetworkService下载完成事件的方案

你当前通过View监听Service变量的方式确实打破了分层逻辑,下面几种方案可以让ViewModel直接接收Service的事件,无需View介入:


1. 使用Combine发布事件(最适配SwiftUI生态)

利用Combine框架的PassthroughSubject让NetworkService发送下载完成事件,ViewModel订阅该事件后处理逻辑并更新自身状态,最终驱动View刷新。

修改NetworkService:

import Combine

class NetworkService: NSObject, ObservableObject {
    // 创建Subject用于发送下载完成事件,携带下载路径URL
    let downloadCompleted = PassthroughSubject<URL, Error>()
    
    // ... 原有代码
}

extension NetworkService: URLSessionDownloadDelegate {
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        print("Download complete.")
        // 发送下载完成事件
        downloadCompleted.send(location)
        
        // 如果下载过程中产生错误,可发送失败事件:
        // downloadCompleted.send(completion: .failure(downloadError))
    }
}

修改ViewModel:

class ContentViewModel: NSObject, ObservableObject {
    @Injected(\.networkService) var networkService
    private var cancellables = Set<AnyCancellable>()
    
    override init() {
        super.init()
        // 订阅Service的下载事件
        networkService.downloadCompleted
            .receive(on: DispatchQueue.main) // 确保在主线程处理UI相关逻辑
            .sink(
                receiveCompletion: { [weak self] completion in
                    if case .failure(let error) = completion {
                        // 处理下载错误
                        print("下载失败:\(error)")
                    }
                },
                receiveValue: { [weak self] location in
                    // 执行下载完成后的逻辑
                    self?.doSomethingAfterDownload(location: location)
                }
            )
            .store(in: &cancellables)
    }
    
    func doSomethingAfterDownload(location: URL) {
        // 这里更新ViewModel的@Published属性,驱动View刷新
        // 例如:@Published var downloadStatus: String = "未开始"
        // downloadStatus = "下载完成:\(location.path)"
    }
}

2. 使用自定义Delegate协议

通过定义协议让ViewModel成为NetworkService的代理,下载完成时Service直接调用代理方法通知ViewModel。

定义协议并修改NetworkService:

// 下载事件代理协议
protocol NetworkServiceDownloadDelegate: AnyObject {
    func networkService(_ service: NetworkService, didFinishDownloadingTo location: URL)
    func networkService(_ service: NetworkService, didFailWithError error: Error)
}

class NetworkService: NSObject, ObservableObject {
    // 弱引用代理,避免循环引用
    weak var delegate: NetworkServiceDownloadDelegate?
    
    // ... 原有代码
}

extension NetworkService: URLSessionDownloadDelegate {
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        print("Download complete.")
        delegate?.networkService(self, didFinishDownloadingTo: location)
    }
    
    // 处理下载错误
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if let error = error {
            delegate?.networkService(self, didFailWithError: error)
        }
    }
}

修改ViewModel遵守协议:

class ContentViewModel: NSObject, ObservableObject, NetworkServiceDownloadDelegate {
    @Injected(\.networkService) var networkService
    
    override init() {
        super.init()
        // 设置Service的代理为自身
        networkService.delegate = self
    }
    
    // 实现代理方法
    func networkService(_ service: NetworkService, didFinishDownloadingTo location: URL) {
        doSomethingAfterDownload(location: location)
    }
    
    func networkService(_ service: NetworkService, didFailWithError error: Error) {
        print("下载失败:\(error)")
    }
    
    func doSomethingAfterDownload(location: URL) {
        // 更新ViewModel状态,驱动View刷新
    }
}

3. 使用闭包回调(轻量方案)

在NetworkService中定义闭包属性,下载完成时直接调用闭包通知ViewModel。

修改NetworkService:

class NetworkService: NSObject, ObservableObject {
    // 下载完成回调闭包
    var onDownloadCompleted: ((URL) -> Void)?
    // 下载失败回调闭包
    var onDownloadFailed: ((Error) -> Void)?
    
    // ... 原有代码
}

extension NetworkService: URLSessionDownloadDelegate {
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        print("Download complete.")
        onDownloadCompleted?(location)
    }
    
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if let error = error {
            onDownloadFailed?(error)
        }
    }
}

修改ViewModel设置闭包:

class ContentViewModel: NSObject, ObservableObject {
    @Injected(\.networkService) var networkService
    
    override init() {
        super.init()
        // 设置下载完成回调
        networkService.onDownloadCompleted = { [weak self] location in
            // 切换到主线程处理UI逻辑
            DispatchQueue.main.async {
                self?.doSomethingAfterDownload(location: location)
            }
        }
        
        // 设置下载失败回调
        networkService.onDownloadFailed = { error in
            print("下载失败:\(error)")
        }
    }
    
    func doSomethingAfterDownload(location: URL) {
        // 更新ViewModel状态
    }
}

方案推荐

优先选择Combine方案,因为它和SwiftUI的ObservableObject生态完全契合,支持线程管理、自动取消订阅等特性,代码更简洁且符合现代Swift编程风格。

内容的提问来源于stack exchange,提问作者blu-Fox

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 06:15:56