談?wù)勅绾问褂肧wift寫出iOS斷點(diǎn)續(xù)傳下載大文件

本篇講述實(shí)現(xiàn)iOS文件下載功能,包含大文件下載,后臺下載,殺死進(jìn)程,重新啟動時繼續(xù)下載,設(shè)置下載并發(fā)數(shù),監(jiān)聽網(wǎng)絡(luò)改變等。

預(yù)覽效果

IMG_0686.gif

附上Demo地址,如果覺得還行呢,就麻煩順手給個star

下載功能的實(shí)現(xiàn)

使用的網(wǎng)絡(luò)連接的類為URLSession。在iOS7時推出,至此iOS系統(tǒng)才有了后臺傳輸。在初始化URLSession前,需要先創(chuàng)建URLSessionConfiguration,可以理解為是URLSession需要的一個配置。URLSessionConfiguration有三種模式:

  • default:可以使用緩存的Cache、Cookie、鑒權(quán)。
  • ephemeral:僅內(nèi)存緩存,不使用緩存的Cache、Cookie、鑒權(quán)。
  • background:支持后臺傳輸,需要一個identifier標(biāo)識,用來重新連接session對象。
let configuration = URLSessionConfiguration.background(withIdentifier: "CXDownloadBackgroundSessionIdentifier"

創(chuàng)建URLSession,設(shè)置配信息、代理、代理線程:

// Create `URLSession`, configure information, proxy, proxy thread.
session = URLSession(configuration: configuration, delegate: self, delegateQueue: queue)

在實(shí)現(xiàn)下載前,還需要了解一個很重要的類:URLSessionTask,無論下載多少文件,我們只需要初始化一個URLSession即可,而每個task對應(yīng)一個任務(wù),需要通過task才能實(shí)現(xiàn)下載,URLSessionTask是一個基類,有四個子類:

  • URLSessionDataTask:下載時,內(nèi)容Data對象返回,需要我們不斷寫入文件。
  • URLSessionUploadTask:繼承URLSessionDataTask,內(nèi)容以Data對象返回,協(xié)議方法中可以查看請求時上傳內(nèi)容的過程,支持后臺傳輸。
  • URLSessionStreamTask:建立了一個TCP/IP連接,替代InputStream/OutputStream,新的API可異步讀寫,自動通過HTTP代理連接遠(yuǎn)程服務(wù)器。
  • URLSessionDownloadTask:推薦使用該task實(shí)現(xiàn)文件下載,斷點(diǎn)續(xù)傳系統(tǒng)幫我們做了,資源會下載到一個臨時文件,下載完成需將文件移動至想要的路徑,系統(tǒng)會刪除臨時路勁文件,暫停時,系統(tǒng)會返回Data對象,恢復(fù)下載時用這個data創(chuàng)建task,支持后臺傳輸。

后臺下載

到這里,已經(jīng)可以通過URLSessionDataTask實(shí)現(xiàn)斷點(diǎn)續(xù)傳了,下面介紹如何實(shí)現(xiàn)后臺下載,其實(shí)非常簡單,一共三步:

  1. 創(chuàng)建URLSession時,需要創(chuàng)建后臺模式URLSessionConfiguration,上面已經(jīng)介紹過了。
  2. 在AppDelegate中實(shí)現(xiàn)下面方法,并定義變量保存completionHandler代碼塊:
// 應(yīng)用處于后臺,所有下載任務(wù)完成調(diào)用
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
    CXDownloadManager.shared.setDidFinishEventsForBackgroundURLSession(completionHandler: completionHandler)
}
  1. 在下載類中實(shí)現(xiàn)下面URLSessionDelegate協(xié)議方法,其實(shí)就是先執(zhí)行完task的協(xié)議,保存數(shù)據(jù)、刷新界面之后再執(zhí)行在AppDelegate中保存的代碼塊:
// 應(yīng)用處于后臺,所有下載任務(wù)完成及URLSession協(xié)議調(diào)用之后調(diào)
public func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
    // Execute the block, the system generates a snapshot in the background, and releases the assertion that prevents the application from being suspended.
    didFinishEventsForBackgroundURLSessionHandler?()
}

程序終止,再次啟動繼續(xù)下載:

后臺下載實(shí)現(xiàn)之后,再看一下如何實(shí)現(xiàn)進(jìn)程殺死后,再次啟動時繼續(xù)下載,在應(yīng)用程序被殺掉時,系統(tǒng)會自動保存應(yīng)用下載session信息,重新啟動應(yīng)用時,如果創(chuàng)建和之前相同identifier的session,系統(tǒng)會找到對應(yīng)的session數(shù)據(jù),并響應(yīng)urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?)方法,操作如下:

 public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        guard let url = task.taskDescription else {
            return
        }
        // Process killed when downloading, callback error when restarting.
        if let err = error as? NSError,
           let reason = err.userInfo[NSURLErrorBackgroundTaskCancelledReasonKey] {
            CXDLogger.log(message: "Reason=\(reason)", level: .info)
            guard let model = CXDownloadDatabaseManager.shared.getModel(by: url)
            else {
                return
            }
            model.state = .waiting
            CXDownloadDatabaseManager.shared.updateModel(model, option: .state)
            return
        }
        let taskProcessor = downloadTaskDict[url]
        taskProcessor?.processSession(task: task, didCompleteWithError: error)
    }

并發(fā)數(shù)設(shè)置

下面介紹一下下載并發(fā)數(shù)的設(shè)置:URLSession本身就支持多任務(wù)同時下載,它會根據(jù)性能內(nèi)部控制同時下載的個數(shù),建議最多設(shè)置5個。一個任務(wù)對應(yīng)一個URLSessionDataTask,所以想多任務(wù)同時下載,需要創(chuàng)建多個task,可以用數(shù)組或字典保存。我們定義變量去記錄當(dāng)前下載文件個數(shù)及用戶設(shè)置的最大下載個數(shù)。

監(jiān)聽網(wǎng)絡(luò)改變

用AFN監(jiān)聽,可以點(diǎn)擊這里查看

為了增加用戶體驗(yàn),往往在設(shè)置中會給用戶一個選項(xiàng), 選擇蜂窩網(wǎng)絡(luò)下是否允許下載。URLSessionConfiguration本身就有一個屬性allowsCellularAccess,默認(rèn)為YES,允許蜂窩網(wǎng)絡(luò)下載。如果不需要用戶隨時變更這個選項(xiàng),是可以用這個屬性。但是對于正在下載的任務(wù),修改這個屬性是無效的,即我們已經(jīng)通過session創(chuàng)建了task對象,開啟了任務(wù),再試圖用。

private func setup() {
        // Creates a database and a table.
        _ = CXDownloadDatabaseManager.shared
        
        currentCount = 0
        let ud = UserDefaults.standard
        let tmaxConcurrentCount = ud.integer(forKey: CXDownloadConfig.maxConcurrentCountKey)
        maxConcurrentCount = tmaxConcurrentCount > 0 ? tmaxConcurrentCount : 1
        allowsCellularAccess = ud.bool(forKey: CXDownloadConfig.allowsCellularAccessKey)
        
        lock = NSLock()
        
        // Single-threaded proxy queue.
        queue = OperationQueue()
        queue.maxConcurrentOperationCount = 1
        
        // Defines the background session identifier.
        let configuration = URLSessionConfiguration.background(withIdentifier: "CXDownloadBackgroundSessionIdentifier")
        // Allows cellular network download, the default is true, which is turned on here. We added a variable to control the user's switching choice.
        configuration.allowsCellularAccess = true
        
        // Create `URLSession`, configure information, proxy, proxy thread.
        session = URLSession(configuration: configuration, delegate: self, delegateQueue: queue)
 }   

所以創(chuàng)建URLSessionConfiguration時把a(bǔ)llowsCellularAccess設(shè)為YES,然后定義一個變量去控制是否允許蜂窩網(wǎng)絡(luò)下載,在網(wǎng)絡(luò)狀態(tài)改變及用戶設(shè)置修改這個選項(xiàng)之后,調(diào)用暫停、開啟任務(wù)。

數(shù)據(jù)保存

用FMDB存儲數(shù)據(jù),可以點(diǎn)擊這里查看

下載速度計(jì)算

聲明兩個變量,一個記錄時間,一個記錄在特定時間內(nèi)接收到的數(shù)據(jù)大小,在接收服務(wù)器返回?cái)?shù)據(jù)的urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data)方法中,統(tǒng)計(jì)接收到數(shù)據(jù)的大小,達(dá)到時間限定時,計(jì)算速度=數(shù)據(jù)/時間,然后清空變量,為方便數(shù)據(jù)庫存儲,這里用的時間戳。

func processSession(dataTask: URLSessionDataTask, didReceive data: Data) {
        let receivedBytes = dataTask.countOfBytesReceived + resumedFileSize
        let allBytes = dataTask.countOfBytesExpectedToReceive + resumedFileSize
        model.totalFileSize = allBytes
        model.tmpFileSize = receivedBytes
        
        let dataLength = data.count
        // Calculates the size of the downloaded file within the speed time.
        model.intervalFileSize += Int64(dataLength)
        
        let intervals = CXDToolbox.getIntervalsWithTimestamp(model.lastSpeedTime)
        if intervals > 1 {
            // Calculates speed
            model.speed = model.intervalFileSize / intervals
            
            model.lastSpeedTime = CXDToolbox.getTimestampWithDate(Date())
        }
        
        let progress = Float(receivedBytes) / Float(allBytes)
        //CXDLogger.log(message: "progress: \(progress)", level: .info)
        model.progress = progress
        
        // Update the specified model in database.
        CXDownloadDatabaseManager.shared.updateModel(model, option: .progressData)
        postProgressNotification()
        // Reset it.
        model.intervalFileSize = 0
}

點(diǎn)贊+關(guān)注,第一時間獲取技術(shù)干貨和最新知識點(diǎn),謝謝你的支持!

最后祝大家生活愉快~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內(nèi)容