2016-11-03 45 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分鐘,他必須回來完成下載。