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
URLSessionis independent of view controllers. You can create a shared globalURLSessioninstance (or multiple instances if needed), and anyUIViewControllercan use this session to start download tasks. TheURLSessionDelegatecallbacks 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:
- Using
downloadTask.originalRequest?.urlto identify unique download tasks is a smart move—since most download URLs are unique, this works perfectly as a key to track task states. - 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. - 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
URLSessionand all download management code into a singleton class (like yourDownloadService). 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
URLSessionDownloadTaskand linking it to the callbacks. - Update UI via callbacks: In
URLSessionDelegatecallbacks (like progress updates inurlSession(_: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
相关产品推荐
相关产品推荐

