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

     2023-03-07     204

关键词:

【中文标题】iOS Swift 在后台下载大量小文件【英文标题】:iOS Swift download lots of small files in background 【发布时间】:2016-11-03 14:21:59 【问题描述】:

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

下载大量(例如 3000 个)小 PNG 文件(例如 5KB) 一个一个 如果应用在后台继续下载 如果图像下载失败(通常是因为 Internet 连接已丢失),请等待 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() 
            
        
    

    // --------------------------------------------------------------------------------

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,不知道这是不是正常现象? 3000 次下载在 startDownload 方法中启动。队列的 maxConcurrentOperationCount 似乎没有得到尊重:下载正在同时运行 downloadsSession.downloadTask(with: url);通话需要 30 毫秒。乘以 3000,需要 1mn30,这是一个大问题:/。等待几秒钟 (2-3) 即可。 我无法在两次下载之间设置延迟(这不是什么大问题。虽然会很好,但如果我不能这样做也可以)

理想情况下,我会异步运行 startDownload 方法,并在 for 循环中同步下载文件。但我想我不能在 iOS 后台执行此操作?

【问题讨论】:

显示不工作的代码。 @MwcsMac,查看我的编辑 【参考方案1】:

这就是我最终所做的:

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

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

【讨论】:

颤振 | Dio Package ...在后台下载大文件

】颤振|DioPackage...在后台下载大文件【英文标题】:Flutter|DioPackage...downloadingLargeFilesinBackground【发布时间】:2020-03-1008:09:03【问题描述】:我正在使用带有dio包的Flutter来下载诸如p​​owerpoint和视频等文件。我想问的是如何在后... 查看详情

iOS - 在后台下载并分配给 UITableViewCell imageView 的图像未填充视图

】iOS-在后台下载并分配给UITableViewCellimageView的图像未填充视图【英文标题】:iOS-ImagethatisdownloadedinthebackgroundandassignedtoUITableViewCellimageViewdoesnotfillview【发布时间】:2013-04-2605:22:26【问题描述】:无法真正回答我的问题的类似问题... 查看详情

iOS 中的后台获取和后台传输以在后台下载数据 (JSON)

】iOS中的后台获取和后台传输以在后台下载数据(JSON)【英文标题】:BackgroundfetchandbackgroundtransfersiniOStodownloaddata(JSON)onbakcground【发布时间】:2014-04-1319:29:56【问题描述】:我正在将我的一个应用程序升级到iOS7并考虑尝试新的多任... 查看详情

IOS NSURLSession 后台上传如何在后台上传大文件?

】IOSNSURLSession后台上传如何在后台上传大文件?【英文标题】:IOSNSURLSessionBackgroundUploadinghowtouploadLargeFilesinBackground?【发布时间】:2017-04-2414:34:55【问题描述】:我想将2-3GB的大文件(视频)上传到后台服务器,要求如下第一种... 查看详情

完成后在后台下载并唤醒应用程序

】完成后在后台下载并唤醒应用程序【英文标题】:Downloadinginbackgroundandwakingappwhenfinished【发布时间】:2017-03-1616:50:39【问题描述】:我使用配置为后台会话的URLSession来下载一些文件。下载完成后,我使用:funcurlSession(_session:URL... 查看详情

下载的PDF文件小且无法打开 - Swift iOS

】下载的PDF文件小且无法打开-SwiftiOS【英文标题】:PDFfiledownloadedwithsmallsizeandcan\'tbeopened-SwiftiOS【发布时间】:2021-12-3012:05:50【问题描述】:我正在尝试使用来自API的链接下载pdf文件,但它总是以5KB的大小下载,并且文件总是损... 查看详情

如何使用 AFNetworking 3.0 在后台下载大文件并在会话完成所有任务时显示本地通知

】如何使用AFNetworking3.0在后台下载大文件并在会话完成所有任务时显示本地通知【英文标题】:HowtodownloadlargefileinbackgroundusingAFNetworking3.0andpresentlocalnotificationwhensessioncompletesallthetasks【发布时间】:2016-02-1115:06:57【问题描述】:... 查看详情

.netmvc中使用webclient在后台下载文件,前台显示进度。

.NETMVC中使用WebClient在后台下载文件,前台显示进度。求解如何实现,这问题纠结很久了的,最好有实例。财富值不够。都不可以悬赏。一般来说前后台的交互式不会设计成这样的,首先需要明确的是前后的交互在MVC里一种是后... 查看详情

java示例代码_Android——如何在指定时间在后台下载数据

java示例代码_Android——如何在指定时间在后台下载数据 查看详情

在 iOS 中在后台上传超过 10 分钟的照片

】在iOS中在后台上传超过10分钟的照片【英文标题】:Uploadphotosinbackgroundformorethan10minutesiniOS【发布时间】:2013-03-2813:02:09【问题描述】:我有一个iPhone应用程序需要在应用程序处于后台时上传照片。目前我在applicationDidEnterBackgrou... 查看详情

j2me如何在后台下载图片?

】j2me如何在后台下载图片?【英文标题】:Howtodownloadimagesinbackgroundinj2me?【发布时间】:2011-12-0814:14:57【问题描述】:我有一个画布,我在其中显示一个图像在中心。此图像是通过url下载的。实际上还有更多的图片要下载,这意... 查看详情

如何使用 Afnetworking 在 ios 中在后台上传图像

】如何使用Afnetworking在ios中在后台上传图像【英文标题】:HowtouploadimagesinthebackgroundiniosusingAfnetworking【发布时间】:2014-09-0607:06:43【问题描述】:我想将图像从iOS应用程序上传到php服务器。我目前在AFNetworkingOperationManager中使用PO... 查看详情

ios8:如何使用 NSURLSession 在后台上传 100 张照片?可用空间问题

】ios8:如何使用NSURLSession在后台上传100张照片?可用空间问题【英文标题】:ios8:Howdoyouupload100sofphotosinbackgroundusingNSURLSession?Freespaceissue【发布时间】:2015-03-2006:48:00【问题描述】:照片应用如何在后台从CameraRoll上传所有内容?... 查看详情

Android:完全在后台上传文件

】Android:完全在后台上传文件【英文标题】:Android:uploadfilestotallyinbackground【发布时间】:2015-05-2712:39:25【问题描述】:我必须创建一个多文件上传(3-7张照片),但是这个上传必须完全由系统管理,这样,即使用户关闭应用程... 查看详情

针对大量小文件优化 S3 下载

】针对大量小文件优化S3下载【英文标题】:OptimizeS3downloadforlargenumberoftinyfiles【发布时间】:2018-04-1814:59:53【问题描述】:我目前使用TransferManager从Lambda函数下载S3存储桶中的所有文件。//InitializeTransferManagerBuildertxBuilder=TransferMan... 查看详情

如何在后台通过ajax加载块?

】如何在后台通过ajax加载块?【英文标题】:Howtoloadblockbyajaxinbackground?【发布时间】:2018-07-0511:38:27【问题描述】:我的模板中有由Nestable2插件创建的树结构。我使用下一个html渲染树结构,您可以在下面看到。它可以工作,但... 查看详情

在后台下载数据的同时,从 JSON 中下载数据并存储到 CoreData 和 Display

】在后台下载数据的同时,从JSON中下载数据并存储到CoreData和Display【英文标题】:DownloaddatafromJSONandstoretoCoreDataandDisplayinthesametimewhendatadownloadsinbackground【发布时间】:2015-04-0907:25:41【问题描述】:我在将数据显示到tableview的同... 查看详情

iOS在一分钟内杀死CallKit VoIP应用程序在后台工作

】iOS在一分钟内杀死CallKitVoIP应用程序在后台工作【英文标题】:iOSkillsCallKitVoIPappworkinginbackgroundinoneminute【发布时间】:2016-11-1707:42:10【问题描述】:我有一个VoIP应用程序,它使用CallKit和PushKit。当应用程序关闭并且iOS收到推送... 查看详情