iOS Swift 在后台下载大量小文件

2024-05-16

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

  • 下载大量(例如 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 后台执行此操作?


所以这就是我最终所做的:

  • 在线程中启动下载,允许在后台运行几分钟(使用 UIApplication.shared.beginBackgroundTask)
  • 使用允许设置超时的自定义下载方法循环下载文件
  • 下载每个文件之前,检查 UIApplication.shared.backgroundTimeRemaining 是否大于 15
  • 如果是,则下载文件,超时时间为 min(60, UIApplication.shared.backgroundTimeRemaining - 5)
  • 如果否,则停止下载并将下载进度保存在用户默认值中,以便在用户导航回应用程序时能够恢复下载
  • 当用户导航回应用程序时,检查状态是否已保存,如果是,则恢复下载。

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

iOS Swift 在后台下载大量小文件 的相关文章

随机推荐

  • 在 ios 版 Ionic 中接收 URL

    我正在使用离子框架 我正在尝试设置一种从另一个应用程序接收网址的方法 就像 您在浏览器中 单击共享 然后将链接发送到另一个应用程序 我的应用程序 我找到了这个cordova https stackoverflow com questions
  • 删除 json 对象字符串中的“\”

    如何删除下面字符串中的特殊字符 String x message content toom recipients id 1000001865 room subject room 我使用了 x replaceAll 但它不起作用 您必须转义正
  • JAX-RS 和 JAX-WS 有什么区别?

    阅读了几篇有关 JAX RS 和 JAX WS 的文章后 我有几个问题想确认一下 JAX RS可以像JAX WS一样做异步请求吗 JAX RS 能否访问不在 Java 平台上运行的 Web 服务 反之亦然 REST 对于配置文件有限的设备
  • Django 将所有未捕获的 url 路由到包含的 urls.py

    我希望每个不以 api 开头的网址都使用 foo urls py urls py from django conf urls import include url from foo import urls as foo urls urlpa
  • 使用 Python NLTK 对大型 (>70MB) TXT 文件进行标记。连接并将数据写入流错误

    首先 我是 python nltk 的新手 所以如果问题太基本 我深表歉意 我有一个大文件 我正在尝试对其进行标记 我遇到内存错误 我读过的一种解决方案是一次一行读取文件 这是有道理的 但是 在这样做时 我收到错误cannot concat
  • 如何使用 Blazor 在 ASP.NET CORE 中更新数据库后刷新网页

    我正在制作一个小型房间预订网络应用程序 我希望在给定的时间间隔内刷新网页 即给定的一分钟或对数据库进行更改时 我发现StateHasChanged 但我真的不知道如何实现它 这里是新手一克诺比 我尝试将其放在将约会添加到日程表的函数中 va
  • 拉拉维尔; “SQLSTATE[HY000] [2002] 连接被拒绝”

    我在 OSX 主机上设置了 homestead 2 0 并使用 Sequel Pro 我可以进行迁移并确认数据已在Sequel Pro中迁移 因此看起来数据库连接没有问题 但是 一旦我尝试从 Laravel 4 2 应用程序获取数据 它就无
  • pandas:按多列分组后创建单一大小和总和列

    我有一个数据框 我在 3 列上进行 groupby 并聚合数字列的总和和大小 运行代码后 df pd DataFrame groupby year cntry state agg size sum 我得到如下内容 现在 我想将尺寸子列与主列
  • HashedWheelTimer 与 ScheduledThreadPoolExecutor 相比以获得更高的性能

    我正在考虑如果您需要在一台机器上的 jvm 内尽可能快地调度大量 非阻塞 任务 则应使用哪种计时器实现 我学过ScheduledThreadPoolExecutor and HashedWheelTimer来源 轮计时器一般文档 和以下是基
  • 将 64 位整数相除,就像被除数左移 64 位一样,无需 128 位类型

    对于令人困惑的标题表示歉意 我不知道如何更好地描述我想要完成的任务 我本质上是在尝试做相反的事情获取 64 位乘法的高半部分 https stackoverflow com questions 28868367 getting the hi
  • Canny 算法:迟滞故障

    我正在写 Canny 算法 我似乎有滞后问题 阈值似乎正在处理 但我的迟滞似乎根本不起作用 以及由于某些奇怪的原因而删除弱的方法 请帮忙 Low 10 High 75 After Hysteresis with problem A edge
  • 为 java 项目创建安装

    我创建了一个 java 项目 它使用数据库来检索 编辑和保存数据 我使用 Netbeans 完成了该项目 现在我想在该项目之外创建一个安装 为此 我想包含与项目一起安装的数据库 我用来连接数据库的代码是 Class forName com
  • Twitter 的推文按钮有回调吗?

    有没有办法在 Twitter 的推文按钮上注册回调 我希望能够跟踪我网站上的哪些特定用户在 Twitter 上发布了链接 我无法添加 onClick 事件 因为它是跨域 iFrame 还有其他想法吗 我见过一种方法 https stacko
  • 丰富嵌入中的提及显示为其字符串

    我遇到的问题是我的机器人在丰富的嵌入中没有正确提及 它似乎根本无法标记用户 提及最终看起来像 lt 601756839956447232 gt It should对用户执行 ping 操作 看起来像 我试过做author toString
  • json 解析器和编码器应如何处理转义的 unicode?

    json 规范允许在 json 字符串 格式为 uXXXX 中转义 unicode 它特别提到受限代码点 非字符 作为有效的转义代码点 这是否意味着解析器应该从包含非字符和受限代码点的字符串生成非法的 unicode 一个例子 key uF
  • React.js:可以在函数组件之外分配 useState() 吗?

    是否有任何语法允许分配useState在功能组件之外 到目前为止我已经尝试过 但没有成功 import React useState useEffect from react import ReactDOM from react dom f
  • 应用程序运行时替换exe文件[重复]

    这个问题在这里已经有答案了 我有一个简单的问题 是否可以在 exe 文件 应用程序文件 运行时替换它 我的意思是我知道当应用程序运行时我不能这样做 但也许可以做类似的事情 执行 application shutdown 替换旧的Exe gt
  • 在Android内存中存储gif图像

    我对安卓还很陌生 我想将图像保存到内存中 然后从内存中检索图像并将其加载到图像视图中 我已使用以下代码成功将图像存储在内存中 void saveImage String fileName img cnt jpg File file new
  • Findbugs 与 Google CodePro AnalytiX(Eclipse 插件)[关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • iOS Swift 在后台下载大量小文件

    在我的应用程序中 我需要下载具有以下要求的文件 下载大量 例如 3000 个 小 PNG 文件 例如 5KB 逐个 如果应用程序在后台继续下载 如果图像下载失败 通常是因为互联网连接丢失 请等待 X 秒然后重试 如果失败Y次 则认为下载失败