iOS Swift在后台下载大量小文件 [英] iOS Swift download lots of small files in background

查看:117
本文介绍了iOS Swift在后台下载大量小文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的应用中,我需要下载具有以下要求的文件:

In my app I need to download files with the following requirements:

  • 下载大量(例如3000个)小的PNG文件(例如5KB)
  • 一对一
  • 如果应用在后台继续下载
  • 如果图像下载失败(通常是由于Internet连接丢失),请等待X秒钟,然后重试.如果失败Y次,则认为下载失败.
  • 能够设置每次下载之间的延迟时间以减少服务器负载

iOS能够做到这一点吗?我正在尝试使用NSURLSession和NSURLSessionDownloadTask,但没有成功(我想避免同时启动3000个下载任务).

Is iOS able to do that? I'm trying to use NSURLSession and NSURLSessionDownloadTask, without success (I'd like to avoid starting the 3000 download tasks at the same time).

MwcsMac请求的一些代码:

some code as requested by MwcsMac:

ViewController:

ViewController:

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() }
            }
        }
    }

    // --------------------------------------------------------------------------------
}

AppDelegate:

AppDelegate:

@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 .它说如果我按主屏幕,然后双击主屏幕以显示应用切换器,则应更新应用屏幕截图.似乎无法可靠地工作.但是,当我重新打开应用程序时,它已更新.从昨天开始有了iPhone,我不知道这是否正常吗?
  • 在startDownload方法中开始了3000次下载.似乎不尊重队列的maxConcurrentOperationCount:下载正在并发运行
  • downloadsSession.downloadTask(网址:);通话需要30毫秒.乘以3000,需要1mn30,这是一个大问题:/.等待几秒钟(2-3)没问题.
  • 我不能在两次下载之间设置延迟(这不是一个大问题.虽然会很好,但是如果我不能,那就可以了)
  • I'm not sure that the background part is working. I followed this tutorial: https://www.raywenderlich.com/110458/nsurlsession-tutorial-getting-started. It says that the app screenshot should be updated if I press home and then double tap home to show the app switcher. Does not seems to work reliably. It is updated when I re-open the app though. Having an iPhone since yesterday, I don't know if this is the normal behavior?
  • the 3000 downloads are started in the startDownload method. The maxConcurrentOperationCount of the queue does not seem to be respected: downloads are running concurrently
  • the downloadsSession.downloadTask(with: url); call takes 30ms. Multiplied by 3000, it takes 1mn30, that's a big problem :/ . Waiting a few seconds (2-3) would be ok.
  • I can't set a delay between two downloads (that's not a big problem. Would be nice though, but if I can't it will be OK)

理想情况下,我将异步运行startDownload方法,并在for循环中同步下载文件.但是我想我无法在iOS的背景下做到这一点?

Ideally, I would run the startDownload method asynchronously, and download the files synchronously in the for loop. But I guess I can't do that in background with iOS?

推荐答案

这就是我最终要做的事情:

So here is what I finally did:

  • 在线程中开始下载,允许其在后台运行几分钟(使用UIApplication.shared.beginBackgroundTask)
  • 使用允许设置超时的自定义下载方法,一个接一个地循环下载文件
  • 在下载每个文件之前,请检查UIApplication.shared.backgroundTimeRemaining是否大于15
  • 如果是,请以至少min(60,UIApplication.shared.backgroundTimeRemaining-5)的超时时间下载文件
  • 如果否,请停止下载并将下载进度保存为用户默认设置,以便当用户导航回该应用时可以继续下载
  • 当用户导航回到应用程序时,请检查状态是否已保存,如果已保存,则继续下载.

这样,当用户离开应用程序时,下载会持续几分钟(在iOS 10上为3),并在这三分钟之前暂停.如果用户将应用程序在后台停留3分钟以上,则必须返回以完成下载.

This way the download continues for a few minutes (3 on iOS 10) when the user leaves the app, and is pause just before these 3 minutes are elapsed. If the user leaves the app in background for more than 3 minutes, he must come back to finish the download.

这篇关于iOS Swift在后台下载大量小文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆