iOS Swift在后台

时间:2016-11-03 14:22:00

标签: ios swift download background

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

  • 下载很多(比如3000)小PNG文件(比如5KB)
  • 逐一
  • 如果应用程序在后台继续下载
  • 如果图像下载失败(通常是因为互联网连接已丢失),请等待X秒并重试。如果它失败了Y次,则认为下载失败。
  • 能够在每次下载之间设置延迟以减少服务器负载

iOS能够做到吗?我试图使用NSURLSession和NSURLSessionDownloadTask,但没有成功(我希望避免同时启动3000个下载任务)。

编辑:MwcsMac要求的一些代码:

的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:

@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(with:url);通话需要30ms。乘以3000,需要1mn30,这是个大问题:/。等几秒钟(2-3)就可以了。
  • 我无法在两次下载之间设置延迟(这不是一个大问题。虽然会很好,但如果我能做到那就没问题了)

理想情况下,我会异步运行startDownload方法,并在for循环中同步下载文件。但我想我不能在iOS背景下做到这一点?

1 个答案:

答案 0 :(得分:3)

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

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

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