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

Swift中能否通过URLSessionDelegate在多个UIViewController中同时独立下载文件?

Absolutely! You totally can pull off simultaneous file downloads across multiple independent UIViewControllers using URLSessionDelegate methods in Swift. Let me walk you through how this works and how to make it solid:

Core Principle

  • URLSession is independent of view controllers. You can create a shared global URLSession instance (or multiple instances if needed), and any UIViewController can use this session to start download tasks. The URLSessionDelegate callbacks will correctly map to their corresponding tasks, regardless of which view controller initiated them.
  • The key is to manage tasks and distinguish callbacks properly to avoid interference between downloads from different VCs.

Breakdown of Your Code Snippet

The urlSession(_:downloadTask:didFinishDownloadingTo:) callback you provided is the core logic for handling completed downloads, and here’s why your approach makes sense:

  1. Using downloadTask.originalRequest?.url to identify unique download tasks is a smart move—since most download URLs are unique, this works perfectly as a key to track task states.
  2. Storing active downloads in downloadService.activeDownloads (a global or singleton-managed dictionary) is critical for cross-VC task management. Never store task states inside a single VC; instead, use an independent service class to keep all download states centralized.
  3. The subsequent file storage logic (like localFilePath(for:) and file operations) is universal—any VC can use the corresponding URL to handle the downloaded file correctly.

Practical Recommendations

  • Encapsulate download logic in a singleton service: Wrap URLSession and all download management code into a singleton class (like your DownloadService). This ensures session reuse and unified control over all tasks (active downloads, progress tracking, completion status).
  • Pass callbacks when starting downloads: When a VC initiates a download, call the singleton service’s method with the target URL, plus progress and completion callbacks. The service will handle creating the URLSessionDownloadTask and linking it to the callbacks.
  • Update UI via callbacks: In URLSessionDelegate callbacks (like progress updates in urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)), the service can find the corresponding callback using the task’s URL and notify the initiating VC to update its UI.
  • Manage memory carefully: When a VC is deallocated, cancel its associated download tasks (if no longer needed) and remove its callbacks to avoid memory leaks.

Example Singleton Download Service

class DownloadService: NSObject, URLSessionDelegate, URLSessionDownloadDelegate {
    static let shared = DownloadService()
    private var session: URLSession!
    var activeDownloads: [URL: DownloadTaskInfo] = [:]
    
    // Struct to track task and its callbacks
    struct DownloadTaskInfo {
        var task: URLSessionDownloadTask
        var progressHandler: ((Double) -> Void)?
        var completionHandler: ((URL?, Error?) -> Void)?
    }
    
    private override init() {
        super.init()
        let config = URLSessionConfiguration.default
        session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
    }
    
    // Method to start a download from any VC
    func startDownload(with url: URL, 
                       progressHandler: ((Double) -> Void)?, 
                       completionHandler: ((URL?, Error?) -> Void)?) {
        guard activeDownloads[url] == nil else { return }
        
        let task = session.downloadTask(with: url)
        let taskInfo = DownloadTaskInfo(task: task, 
                                       progressHandler: progressHandler, 
                                       completionHandler: completionHandler)
        activeDownloads[url] = taskInfo
        task.resume()
    }
    
    // Method to cancel a download
    func cancelDownload(with url: URL) {
        activeDownloads[url]?.task.cancel()
        activeDownloads.removeValue(forKey: url)
    }
    
    // MARK: - URLSessionDownloadDelegate
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        guard let sourceURL = downloadTask.originalRequest?.url,
              let taskInfo = activeDownloads[sourceURL] else { return }
        
        let destinationURL = localFilePath(for: sourceURL)
        let fileManager = FileManager.default
        
        do {
            try fileManager.moveItem(at: location, to: destinationURL)
            taskInfo.completionHandler?(destinationURL, nil)
        } catch {
            taskInfo.completionHandler?(nil, error)
        }
        
        activeDownloads.removeValue(forKey: sourceURL)
    }
    
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
        guard let sourceURL = downloadTask.originalRequest?.url,
              let taskInfo = activeDownloads[sourceURL], totalBytesExpectedToWrite > 0 else { return }
        
        let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
        // Dispatch to main thread for UI updates
        DispatchQueue.main.async {
            taskInfo.progressHandler?(progress)
        }
    }
    
    // Helper to get local file path
    private func localFilePath(for url: URL) -> URL {
        let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        return documentsPath.appendingPathComponent(url.lastPathComponent)
    }
}

How to Use in a View Controller

class FirstViewController: UIViewController {
    func startSampleDownload() {
        guard let url = URL(string: "https://example.com/file1.pdf") else { return }
        
        DownloadService.shared.startDownload(with: url, progressHandler: { progress in
            // Update your VC's progress UI (e.g., a progress bar)
            print("First VC download progress: \(String(format: "%.2f%%", progress * 100))")
        }, completionHandler: { [weak self] destinationURL, error in
            guard let self = self else { return }
            if let error = error {
                print("First VC download failed: \(error.localizedDescription)")
            } else if let url = destinationURL {
                print("First VC download finished: \(url.path)")
                // Handle the downloaded file here
            }
        })
    }
}

class SecondViewController: UIViewController {
    func startAnotherDownload() {
        guard let url = URL(string: "https://example.com/file2.mp4") else { return }
        
        DownloadService.shared.startDownload(with: url, progressHandler: { progress in
            print("Second VC download progress: \(String(format: "%.2f%%", progress * 100))")
        }, completionHandler: { [weak self] destinationURL, error in
            guard let self = self else { return }
            if let error = error {
                print("Second VC download failed: \(error.localizedDescription)")
            } else if let url = destinationURL {
                print("Second VC download finished: \(url.path)")
            }
        })
    }
}

内容的提问来源于stack exchange,提问作者Safiqul Islam

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 06:41:52