Swift6中View成员函数使用Delegate的问题及解决方案
解决Swift6中DownloadHelper的Sendable报错及更优设计方案
一、快速修复Sendable报错问题
Swift6对隐式Sendable类型的存储属性要求更严格,NSKeyValueObservation本身不遵循Sendable协议,导致你的DownloadHelper类不符合Sendable要求。可以通过两种方式解决:
1. 标记属性为@unchecked Sendable
如果能确保progressObservation的访问是线程安全的(URLSession的Delegate回调默认在主队列执行),可直接给属性添加@unchecked Sendable标记:
final class DownloadHelper: NSObject, URLSessionTaskDelegate, Sendable { @unchecked Sendable var progressObservation: NSKeyValueObservation! public func urlSession(_ session: URLSession, didCreateTask task: URLSessionTask) { progressObservation = task.progress.observe(\.fractionCompleted) { progress, value in print("Taskprogress \(progress.fractionCompleted)") } } }
注意:
@unchecked Sendable是手动向编译器保证属性的线程安全,需确认不会出现并发访问冲突。
2. 替换KVO为Combine(推荐)
KVO属于老旧API,Combine框架提供了更Swift化的监听方式,且相关类型大多支持Sendable:
import Combine final class DownloadHelper: NSObject, URLSessionTaskDelegate, Sendable { private var cancellables = Set<AnyCancellable>() public func urlSession(_ session: URLSession, didCreateTask task: URLSessionTask) { task.progress.publisher(for: \.fractionCompleted) .sink { progress in print("Taskprogress \(progress)") } .store(in: &cancellables) } }
Set<AnyCancellable>天然兼容Sendable,无需额外标记即可让整个类符合要求。
二、更优的设计模式:将业务逻辑从View抽离到ViewModel
View作为值类型,仅负责UI渲染和交互响应,不适合持有Delegate实例或处理下载这类业务逻辑。更合理的做法是把下载逻辑封装到ViewModel(遵循ObservableObject的引用类型)中:
1. 封装下载逻辑的ViewModel
import Combine import Foundation class DownloadViewModel: ObservableObject { @Published var progress: Double = 0.0 private var cancellables = Set<AnyCancellable>() private let downloadHelper = DownloadHelper() func downloadSomething() async throws { let session = URLSession(configuration: .default, delegate: downloadHelper, delegateQueue: .main) let (localUrl, _) = try await session.download(from: URL(string: "myURL")!) // 下载完成后的处理 print("Downloaded to \(localUrl)") } // 嵌套的Delegate类,直接与ViewModel关联 private final class DownloadHelper: NSObject, URLSessionTaskDelegate, Sendable { weak var viewModel: DownloadViewModel? public func urlSession(_ session: URLSession, didCreateTask task: URLSessionTask) { task.progress.publisher(for: \.fractionCompleted) .receive(on: DispatchQueue.main) .sink { [weak self] fraction in self?.viewModel?.progress = fraction } .store(in: &(self?.viewModel?.cancellables)!) } } init() { downloadHelper.viewModel = self } }
2. 在View中使用ViewModel
struct MyView: View { @StateObject private var viewModel = DownloadViewModel() var body: some View { VStack { Button("开始下载") { Task { try await viewModel.downloadSomething() } } ProgressView(value: viewModel.progress) } } }
这种设计的优势:
- 分离UI与业务逻辑,View仅需响应ViewModel的状态变化
- ViewModel作为引用类型,适合持有Delegate实例和管理订阅生命周期
- 天然适配SwiftUI的状态更新机制(
@Published+ObservableObject)
三、进阶方案:使用Actor处理并发安全
如果下载逻辑涉及复杂并发操作,可使用Actor封装DownloadHelper,确保所有操作线程安全:
actor DownloadHelper: URLSessionTaskDelegate { private var cancellables = Set<AnyCancellable>() var progressHandler: ((Double) -> Void)? nonisolated public func urlSession(_ session: URLSession, didCreateTask task: URLSessionTask) { task.progress.publisher(for: \.fractionCompleted) .receive(on: DispatchQueue.main) .sink { [weak self] fraction in Task { await self?.progressHandler?(fraction) } } .store(in: &cancellables) } }
在ViewModel中使用该Actor时,需通过await访问,完全符合Swift6的并发安全要求。
内容的提问来源于stack exchange,提问作者Chris
相关产品推荐
相关产品推荐

