Alamofire斷點續(xù)傳

當我們下載文件時,暫停,繼續(xù),取消和斷點續(xù)傳以及失敗處理都是必不可少的. 那么我們使用 Alamofire 當然必須要知道應該如何處理.

一 . 單例封裝下載管理者

import UIKit
import Alamofire

class LBDowloadManager: NSObject {
    static let shared = LBDowloadManager()
    var currentDownloadRequest: DownloadRequest?
    var resumeData: Data?
    var downloadTasks: Array<URLSessionDownloadTask>?
    var filePath: URL{
        return FileManager.default.urls(for: .documentDirectory, in: FileManager.SearchPathDomainMask.userDomainMask).first!.appendingPathComponent("LBDownload")
    }
    
    
    //MARK: - 單利方便獲取
    let manager: SessionManager = {
        let configuration = URLSessionConfiguration.background(withIdentifier: "LB.AlamofireDowload")
        configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
        configuration.sharedContainerIdentifier = "group.com.LB.AlamofireDowload"
        let manager = SessionManager(configuration: configuration)
        manager.startRequestsImmediately = true
        manager.backgroundCompletionHandler = {
            debugPrint("后臺完成回來了")
        }
        return manager
    }()

開始下載

func LBDowload(_ url: URLConvertible) -> DownloadRequest {
    currentDownloadRequest = LBDowloadManager.shared.manager.download(url) { [weak self](url, reponse) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
        let fileUrl = self?.filePath.appendingPathComponent(reponse.suggestedFilename!)
        return (fileUrl!,[.removePreviousFile, .createIntermediateDirectories] )
    }
    return currentDownloadRequest!
}

暫停

func suspend() {
    self.currentDownloadRequest?.suspend()
}

對應的是父類 Requestsuspend 方法.

open func suspend() {
    guard let task = task else { return }

    task.suspend()

    NotificationCenter.default.post(
        name: Notification.Name.Task.DidSuspend,
        object: self,
        userInfo: [Notification.Key.Task: task]
    )
}

繼續(xù)

func resume() {
    self.currentDownloadRequest?.resume()
}

對應的是父類 Requestresume 方法.

open func resume() {
    guard let task = task else { delegate.queue.isSuspended = false ; return }

    if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }

    task.resume()

    NotificationCenter.default.post(
        name: Notification.Name.Task.DidResume,
        object: self,
        userInfo: [Notification.Key.Task: task]
    )
}

取消

func cancel() {
    self.currentDownloadRequest?.cancel()
}

這里調用的不再是父類 Requestcancel 方法. 因為子類 DownloadRequest 重寫了這個方法.

/// Cancels the request.
open override func cancel() {
    downloadDelegate.downloadTask.cancel { self.downloadDelegate.resumeData = $0 }

    NotificationCenter.default.post(
        name: Notification.Name.Task.DidCancel,
        object: self,
        userInfo: [Notification.Key.Task: task as Any]
    )
}

它自動保存了 resumeData.

這個 resumeData 到底是什么, 有興趣的同學可以開始任務后暫停, 獲取一下這個 resumeData, 把其寫入到沙盒中, 然后后綴改為 .plist . 然后直接打開查看. 其實里面包含了所有信息, 包括總大小, 已下載數(shù)據(jù)情況, 下載鏈接. 等等. 也就是說我們可以根據(jù)這個 resumeData 給一個新任務去繼續(xù)下載之前的任務.

那么我們在取消之后希望繼續(xù)下載, 保持以前的進度. 則需要在開始方法中增加如下處理.

func LBDowload(_ url: URLConvertible) -> DownloadRequest {
    
     if let resumeData = LBDowloadManager.shared.currentDownloadRequest?.resumeData {
         currentDownloadRequest = LBDowloadManager.shared.manager.download(resumingWith: resumeData)
     }else{
         currentDownloadRequest = LBDowloadManager.shared.manager.download(url) { [weak self](url, reponse) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
             let fileUrl = self?.filePath.appendingPathComponent(reponse.suggestedFilename!)
             return (fileUrl!,[.removePreviousFile, .createIntermediateDirectories] )
         }
     }

    return currentDownloadRequest!
}

即,當 DownloadRequest 有對應的恢復數(shù)據(jù)時, 則拿到這個數(shù)據(jù)繼續(xù)下載.

清理沙盒中數(shù)據(jù)

func clear() {
    let filePath = NSHomeDirectory()+"/Documents/LBDownload"
    if FileManager.default.fileExists(atPath: filePath) {
        try! FileManager.default.removeItem(at: self.filePath)
        print("清理完成")
    }
}

二 . 用戶殺死APP 重新打開續(xù)傳

當我們實際在下載中關閉app, 然后重新打開操作時, 通過在 TaskDelegatetaskDidCompleteWithError 回調代理的斷點發(fā)現(xiàn), 它會觸發(fā).

那么你想到了什么, 是的,我們在外部自己指定回調觸發(fā)事件. 這個 request 的流程 , 啟動 -> 響應 -> 回調. 不熟悉的可以去閱讀一下
Alamofire之Request(二)和隊列執(zhí)行順序分析
Alamofire之Request(一)

那么我們在單例 manager 中, 增加一個 taskDidComplete 的監(jiān)聽閉包.

// 用戶主動關閉app 再重新進來時會觸發(fā).
manager.delegate.taskDidComplete = { (seesion,task, error) in
    if let error = error {
        print("taskDidComplete的error情況: \(error)")
        if let resumeData = (error as NSError).userInfo[NSURLSessionDownloadTaskResumeData] as? Data {
            // resumeData 存儲
            LBDowloadManager.shared.resumeData = resumeData
            print("來了")
        }
    }else{
        print("taskDidComplete的task情況: \(task)")
    }
}

這里要提一句的是 如何獲取已下載的數(shù)據(jù) ResumeData. 是通過閉包中返回的 Error 轉換成 NSError 中的 userInfo 參數(shù)里面獲取到的.

那么拿到了 ResumeData, 就好辦了. 在開始下載的按鈕方法中再增加一個處理

func LBDowload(_ url: URLConvertible) -> DownloadRequest {
    
    if self.resumeData != nil {
        //用戶主動關閉 再次打開續(xù)傳
        currentDownloadRequest = LBDowloadManager.shared.manager.download(resumingWith: self.resumeData!)
    }else{
        if let resumeData = LBDowloadManager.shared.currentDownloadRequest?.resumeData {
            // 用戶取消后續(xù)傳
            currentDownloadRequest = LBDowloadManager.shared.manager.download(resumingWith: resumeData)
        }else{
            //正常開啟新任務
            currentDownloadRequest = LBDowloadManager.shared.manager.download(url) { [weak self](url, reponse) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
                let fileUrl = self?.filePath.appendingPathComponent(reponse.suggestedFilename!)
                return (fileUrl!,[.removePreviousFile, .createIntermediateDirectories] )
            }
        }
    }
    return currentDownloadRequest!
}

至此, 主動關閉 APP , 再次打開實現(xiàn)續(xù)傳就完成了. 那么如果是應用發(fā)生閃退呢?

三 . 應用程序發(fā)生閃退續(xù)傳.

通過實驗驗證發(fā)現(xiàn)其實 URLSessionDownloadTask 在程序發(fā)生閃退時, 其實還是會繼續(xù)下載的. 也就是說, 任務不會因為閃退而終止. 那么就不存在續(xù)傳的問題了.
那么我們如何獲取到下載的文件呢?

我們同樣新增了一個回調 downloadTaskDidFinishDownloadingToURL , 那么我們來實際操作驗證一下.

  • 首先在 VC 中創(chuàng)造一個數(shù)組越界以便崩潰.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    let array = [1]
    print(array[2])
}
  • 然后 manager 增加下載完成回調響應
manager.delegate.downloadTaskDidFinishDownloadingToURL = { (session, downloadTask, url) in

    guard let response = downloadTask.response as? HTTPURLResponse else {return}

    let fileUrl = LBDowloadManager.shared.filePath.appendingPathComponent(response.suggestedFilename!)
    print("轉移路徑:\(fileUrl)")
    do {
        if FileManager.default.fileExists(atPath: fileUrl.path) {
            try FileManager.default.removeItem(at: fileUrl)
        }
        let directory = fileUrl.deletingLastPathComponent()
        try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)

        try FileManager.default.moveItem(at: url, to: fileUrl)
        print("文件移動成功")
    } catch {
        print("文件移動出錯了 \(error)")
    }
}

從新任務開始下載, 快下載完成, 點擊屏幕, 閃退, 然后重新運行, 等待一會兒發(fā)現(xiàn):


來了! . 看下控制臺 以及沙盒文件.


  • 完美. 程序 crash 獲取下載文件.
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市绳矩,隨后出現(xiàn)的幾起案子蠕嫁,更是在濱河造成了極大的恐慌,老刑警劉巖饿凛,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡颜凯,警方通過查閱死者的電腦和手機舀奶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門暑竟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人育勺,你說我怎么就攤上這事但荤÷掎” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵腹躁,是天一觀的道長桑包。 經(jīng)常有香客問我,道長纺非,這世上最難降的妖魔是什么哑了? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮烧颖,結果婚禮上垒手,老公的妹妹穿的比我還像新娘。我一直安慰自己倒信,他們只是感情好科贬,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鳖悠,像睡著了一般榜掌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上乘综,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天憎账,我揣著相機與錄音,去河邊找鬼卡辰。 笑死胞皱,一個胖子當著我的面吹牛,可吹牛的內容都是我干的九妈。 我是一名探鬼主播反砌,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼萌朱!你這毒婦竟也來了宴树?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤晶疼,失蹤者是張志新(化名)和其女友劉穎酒贬,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體翠霍,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡锭吨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了寒匙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片零如。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出埠况,到底是詐尸還是另有隱情,我是刑警寧澤棵癣,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布辕翰,位于F島的核電站,受9級特大地震影響狈谊,放射性物質發(fā)生泄漏喜命。R本人自食惡果不足惜敦迄,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一氛驮、第九天 我趴在偏房一處隱蔽的房頂上張望同蜻。 院中可真熱鬧靶壮,春花似錦匹中、人聲如沸榜揖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽务甥。三九已至牡辽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間敞临,已是汗流浹背态辛。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留挺尿,地道東北人奏黑。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像编矾,于是被迫代替她去往敵國和親熟史。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內容