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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 20:42:03