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
相关产品推荐
相关产品推荐

