在我的应用程序中,我需要下载具有以下要求的文件:
- 下载大量(例如 3000 个)小 PNG 文件(例如 5KB)
- 逐个
- 如果应用程序在后台继续下载
- 如果图像下载失败(通常是因为互联网连接丢失),请等待 X 秒然后重试。如果失败Y次,则认为下载失败。
- 能够设置每次下载之间的延迟以减少服务器负载
iOS 可以做到这一点吗?我正在尝试使用 NSURLSession 和 NSURLSessionDownloadTask,但没有成功(我想避免同时启动 3000 个下载任务)。
编辑:MwcsMac 要求的一些代码:
视图控制器:
class ViewController: UIViewController, URLSessionDelegate, URLSessionDownloadDelegate {
// --------------------------------------------------------------------------------
// MARK: Attributes
lazy var downloadsSession: URLSession = {
let configuration = URLSessionConfiguration.background(withIdentifier:"bgSessionConfigurationTest");
let session = URLSession(configuration: configuration, delegate: self, delegateQueue:self.queue);
return session;
}()
lazy var queue:OperationQueue = {
let queue = OperationQueue();
queue.name = "download";
queue.maxConcurrentOperationCount = 1;
return queue;
}()
var activeDownloads = [String: Download]();
var downloadedFilesCount:Int64 = 0;
var failedFilesCount:Int64 = 0;
var totalFilesCount:Int64 = 0;
// --------------------------------------------------------------------------------
// --------------------------------------------------------------------------------
// MARK: Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
startButton.addTarget(self, action:#selector(onStartButtonClick), for:UIControlEvents.touchUpInside);
_ = self.downloadsSession
_ = self.queue
}
// --------------------------------------------------------------------------------
// --------------------------------------------------------------------------------
// MARK: User interaction
@objc
private func onStartButtonClick() {
startDownload();
}
// --------------------------------------------------------------------------------
// --------------------------------------------------------------------------------
// MARK: Utils
func startDownload() {
downloadedFilesCount = 0;
totalFilesCount = 0;
for i in 0 ..< 3000 {
let urlString:String = "http://server.url/\(i).png";
let url:URL = URL(string: urlString)!;
let download = Download(url:urlString);
download.downloadTask = downloadsSession.downloadTask(with: url);
download.downloadTask!.resume();
download.isDownloading = true;
activeDownloads[download.url] = download;
totalFilesCount += 1;
}
}
// --------------------------------------------------------------------------------
// --------------------------------------------------------------------------------
// MARK: URLSessionDownloadDelegate
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if(error != nil) { print("didCompleteWithError \(error)"); }
failedFilesCount += 1;
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
if let url = downloadTask.originalRequest?.url?.absoluteString {
activeDownloads[url] = nil
}
downloadedFilesCount += 1;
[eventually do something with the file]
DispatchQueue.main.async {
[update UI]
}
if(failedFilesCount + downloadedFilesCount == totalFilesCount) {
[all files have been downloaded]
}
}
// --------------------------------------------------------------------------------
// --------------------------------------------------------------------------------
// MARK: URLSessionDelegate
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
if let completionHandler = appDelegate.backgroundSessionCompletionHandler {
appDelegate.backgroundSessionCompletionHandler = nil
DispatchQueue.main.async { completionHandler() }
}
}
}
// --------------------------------------------------------------------------------
}
应用程序委托:
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var backgroundSessionCompletionHandler: (() -> Void)?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
backgroundSessionCompletionHandler = completionHandler
}
}
下载:
class Download: NSObject {
var url: String
var isDownloading = false
var progress: Float = 0.0
var downloadTask: URLSessionDownloadTask?
var resumeData: Data?
init(url: String) {
self.url = url
}
}
这段代码有什么问题:
- 我不确定背景部分是否正常工作。我按照这个教程进行操作:https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started。它说,如果我按主页,然后双击主页以显示应用程序切换器,则应更新应用程序屏幕截图。似乎工作不可靠。当我重新打开应用程序时它会更新。从昨天开始就有iPhone了,不知道这是否正常?
- 3000次下载是在startDownload方法中启动的。队列的 maxConcurrentOperationCount 似乎不受尊重:下载同时运行
- downloadsSession.downloadTask(with: url);呼叫需要 30 毫秒。乘以 3000,需要 1mn30,这是一个大问题:/ 。等待几秒钟(2-3)就可以了。
- 我无法设置两次下载之间的延迟(这不是一个大问题。虽然很好,但如果我不能,那就没问题了)
理想情况下,我会异步运行 startDownload 方法,并在 for 循环中同步下载文件。但我想我不能在 iOS 后台执行此操作?