2016-11-03 48 views
2

在我的应用程序,我需要有以下要求下载文件:iOS版雨燕下载大量的背景小文件

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

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

编辑:

的ViewController:通过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之后,我不知道这是否是正常行为?
  • 在startDownload方法中启动了3000次下载。队列的maxConcurrentOperationCount似乎并未受到尊重:下载正在并发运行
  • downloadsSession.downloadTask(with:url);通话需要30ms。乘以3000,需要1mn30,这是一个大问题:/。等待几秒钟(2-3)就可以了。
  • 我不能同时设置两个下载之间的延迟(这不是一个大问题。会很好,虽然,但如果我不能马上就好)

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

+0

显示不工作的代码。 – MwcsMac

+0

@MwcsMac,看我的编辑 –

回答

3

所以这是我终于做到:

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

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