qNSURLSession系列筆記:
NSURLSession筆記 上傳文件
使用NSURLSession下載文件
NSURLSession 和 NSURLConnection 的比較 配合之前寫的NSURLConnection下載文件筆記,有所體會庆揩。如果服務器返回的是一些比較大的數(shù)據(jù)贸人,NSUrlSession 的下載做的是最好的,不需要去考慮什么邊下載邊寫入沙盒的問題,這些都封裝好了鲸阔。
DownloadTask支持BackgroundSession偷霉,而dataTask不支持
DownloadTask支持斷點續(xù)傳(下載到一半的時候暫停,重啟后繼續(xù)下載褐筛,前提下載的服務器支持斷點續(xù)傳)
使用block回調(diào)
- (void)test{
NSURL *url = [NSURL URLWithString:@"xxxxx"];
NSURLSession *session = [NSURLSession sharedSession];
//NSURLResponse響應頭类少,真實類型是NSHTTPURLResponse
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%@",location);
}];
[task resume];
}
- 在塊代碼回調(diào)的方式,回調(diào)的執(zhí)行線程是異步的(block在子線程中調(diào)用渔扎,如果拿到數(shù)據(jù)后要做一些UI更新操作硫狞,就要回到主線程刷新)。
- 回調(diào)中l(wèi)ocation輸出的是一個文件路徑晃痴,通過打斷點妓忍,可以看到要下載的文件在被下載時存在于該路徑下,但代碼塊執(zhí)行完畢后就被刪除了愧旦。原因:
這里block的location參數(shù)在api中的描述如下:The location of a temporary file where the server’s response is stored. You must move this file or open it for reading before your completion handler returns. Otherwise, the file is deleted, and the data is lost.
下載文件會保存在沙盒的tmp文件下世剖,如果在回調(diào)方法中,不做任何處理笤虫,下載的文件會被刪除旁瘫。這樣設計的目的:通常從網(wǎng)絡上下載文件,zip格式文件最多琼蚯,這樣可以替用戶節(jié)約流量酬凳;如果是zip包,下載之后遭庶,需要解壓縮宁仔,解壓之后原始的zip就不需要了,系統(tǒng)會自動幫我們刪除初始zip文件峦睡。
因此需要作出如下修改:
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%@",location);//tmp路徑
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// response.suggestedFilename : 建議的文件名
NSString *file = [caches stringByAppendingPathComponent:response.suggestedFilename];
// 將臨時文件move或者copy 到Caches文件夾
// AtPath : 剪切前的文件路徑 ToPath : 剪切后的文件路徑
[[NSFileManager defaultManager] moveItemAtPath:location.path toPath:file error:nil];
}];
沙盒目錄:
Documents:應用中用戶數(shù)據(jù)可以放在這里翎苫,iTunes備份和恢復的時候會包括此目錄权埠。
[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]
tmp:存放臨時文件,iTunes不會備份和恢復此目錄煎谍,此目錄下文件可能會在應用退出后刪除
Library/Caches:存放緩存文件攘蔽,iTunes不會備份此目錄,此目錄下文件不會在應用退出刪除呐粘。
NSString *cacheStr = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
下載進度跟進:使用代理NSURLSessionDownloadDelegate
除了NSURLSessionDelegate用來處理Session層次的事件满俗,NSURLSessionTaskDelegate處理Task的共性事件之外,還有NSURLSessionDownloadTaskDelegate 用來特別處理Download事件
- 要使用代理就不能使用回調(diào)代碼塊
- 如果要跟進下載進度(也就是使用代理),不能使用全局session:sharesession
- NSURLSessionConfiguration 提供了一個全局的網(wǎng)絡環(huán)境配置作岖,包括:身份驗證唆垃,瀏覽器類型,cookie痘儡,緩存辕万,超時時長。一旦設置可以全局共享谤辜,替代NSURLRequset中的附加信息蓄坏!
- 下面的代碼中,下載的文件在下載時存在于沙盒tmp文件夾下
- 沒有內(nèi)存峰值問題
//全局網(wǎng)絡會話丑念,管理所有網(wǎng)絡任務
@property (nonatomic , strong) NSURLSession *session;
//下載任務
@property (nonatomic , strong) NSURLSessionDownloadTask *downloadTask;
- (NSURLSession *)session{
if (_session == nil) {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
}
return _session;
}
//開始
- (void)start{
NSURL *url = [NSURL URLWithString:@"xxxxx"];
//要使用代理就不能使用回調(diào)代碼塊
//如果在塊代碼回調(diào)的方式涡戳,回調(diào)的執(zhí)行線程是異步的。
self.downloadTask = [self.session downloadTaskWithURL:url];
[self.downloadTask resume];
}
- 代理方法
//在iOS7中三個代理方法都是必須的脯倚,到了8.0只有第一個是必須的
//要支持iOS7&8渔彰,三個方法都要實現(xiàn)
//下載完成方法,一定要在這個函數(shù)返回之前推正,對數(shù)據(jù)進行使用恍涂,或者保存
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
NSLog(@"finish %@",location);
//生成沙盒的路徑,對數(shù)據(jù)進行保存
NSArray *docs = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [docs[0] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
NSURL *toURL = [NSURL fileURLWithPath:path];
[[NSFileManager defaultManager] copyItemAtURL:location toURL:toURL error:nil];
}
//下載進度
/*
bytesWritten 本次下載的字節(jié)數(shù)
totalBytesWritten 已經(jīng)下載的字節(jié)數(shù)
totalBytesExpectedToWrite 期望下載的字節(jié)數(shù):文件總大小
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
float progress = (float)totalBytesWritten/totalBytesExpectedToWrite;
NSLog(@"%f",progress);
}
//下載續(xù)傳數(shù)據(jù)
//resume之后會調(diào)用這個方法
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{
}
//不管什么類型的task結(jié)束植榕,URLSession:task:didCompleteWithError:都會被調(diào)用再沧,根據(jù)error是否為空判斷成功失敗
//用戶取消下載,調(diào)用cancelByProducingResumeData:也會調(diào)用這個代理方法
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error{
self.resumeData = error.userInfo[NSURLSessionDownloadTaskResumeData];
}
代理的方式能監(jiān)聽下載進度尊残,不會將數(shù)據(jù)寫入緩存炒瘸,所以適合大文件,而block的方式將數(shù)據(jù)寫入緩存寝衫,不適合大文件下載
斷點續(xù)傳
NSURLConnection需要手動設置請求頭的Range的方式實現(xiàn),NSURLSession的resumeData已經(jīng)包含了顷扩,也就是NSURLSession已經(jīng)實現(xiàn)了。
1.開始
//開始
- (IBAction)start{
NSURL *url = [NSURL URLWithString:@"xxxxx"];
self.downloadTask = [self.session downloadTaskWithURL:url];
[self.downloadTask resume];
}
2.暫停:NSURLSessionDownloadTask的- cancelByProducingResumeData:(void (^)(NSData * __nullable resumeData))completionHandler
回調(diào)block里參數(shù)resumeData包含了繼續(xù)下載文件的位置信息慰毅,下次繼續(xù)下載的時候是從這個位置開始隘截。
//暫停
- (IBAction)pause{
//取消下載任務
[self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
//參數(shù)resumeData:續(xù)傳的數(shù)據(jù)(已經(jīng)下載下來的數(shù)據(jù))
NSLog(@"數(shù)據(jù)長度 %tu", resumeData.length);
//釋放下載任務
//self.downloadTask = nil;
}];
}
- 如果任務已經(jīng)被暫停(點擊了一次暫停鍵),不應該能夠再次被暫停(再點一次暫停按鈕)。上面這樣寫婶芭,當?shù)诙吸c暫停鍵的時候會打印出:“數(shù)據(jù)長度 0”
因此在暫停之后要釋放下載任務:self.downloadTask = nil;
- 由于在第3步“繼續(xù)下載”(見下)中需要使用到續(xù)傳數(shù)據(jù)东臀,因此需要把續(xù)傳數(shù)據(jù)保存起來。
@property (nonatomic , strong) NSData *resumeData;
- (IBAction)pause{
[self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
//resumeData:續(xù)傳的數(shù)據(jù)
NSLog(@"數(shù)據(jù)長度 %tu", resumeData.length);
self.resumeData = resumeData;
//釋放下載任務//如果任務已經(jīng)被暫停雕擂,不應該能夠再次被暫停(連點兩次暫停按鈕)
self.downloadTask = nil;
}];
}
- 這里是否涉及block循環(huán)引用的問題啡邑?是的贱勃,有循環(huán)引用問題井赌。self對task進行了強引用,task又對block進行了引用贵扰,block又對self進行引用仇穗,這就形成了循環(huán)使用。
解決方法:對self進行弱引用__weak typeof(self) ws = self; -
cancelByProducingResumeData:
取消任務是不能恢復的戚绕,只能重新創(chuàng)建任務纹坐。
3.繼續(xù)
//繼續(xù)
- (IBAction)resume{
//使用“續(xù)傳數(shù)據(jù)”啟動下載任務,使用的是之前保存的續(xù)傳數(shù)據(jù)
//creat a new task
self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
[self.downloadTask resume];
}
- 使用之前保存的續(xù)傳數(shù)據(jù)來啟動下載任務舞丛,這里的downloadTask是新創(chuàng)建的耘子,和之前那個不是同一個。另外因為所有的任務task默認都是掛起不執(zhí)行的球切,所以最后要resume一下谷誓。
- 運行程序,點擊 開始->暫停->繼續(xù)(執(zhí)行到這里可以正常續(xù)傳)->繼續(xù)(這是任務會回到從“續(xù)傳數(shù)據(jù)”的地方開始下載吨凑,放個進度條控件就可以看見了捍歪。。鸵钝。)
續(xù)傳數(shù)據(jù)的作用就是建立新的下載任務糙臼,一旦下載任務建立以后,續(xù)傳數(shù)據(jù)就沒有用了恩商。因此重新建立下載任務之后要清空續(xù)傳數(shù)據(jù)self.resumeData = nil
- (IBAction)resume{
if (self.resumeData == nil) {
NSLog(@"沒有暫停的任務");
return;
}
//使用“續(xù)傳數(shù)據(jù)”啟動下載任務变逃,使用的是之前保存的續(xù)傳數(shù)據(jù)
//續(xù)傳數(shù)據(jù)的作用就是建立新的下載任務,一旦下載任務建立以后怠堪,續(xù)傳數(shù)據(jù)就沒有用了
self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
//清空續(xù)傳數(shù)據(jù)
self.resumeData = nil;
[self.downloadTask resume];
}
tips:
這種斷點下載只支持應用內(nèi)斷點,如果程序在下載過程中途關(guān)閉,則不能恢復下載
- 下載失敗后如何恢復下載揽乱?
不管什么類型的task結(jié)束,URLSession:task:didCompleteWithError:
都會被調(diào)用研叫,根據(jù)error是否為空判斷成功失敗锤窑。在任務失敗的情況下,大多數(shù)app應當嘗試重新請求直到用戶取消任務或者服務端返回error code于是這個任務不會成功嚷炉。
NSError對象的userInfo字典包含一個Key值為NSURLSessionDownloadTaskResumeData
的value渊啰,應將這個值傳給downloadTaskWithResumeData:
或者downloadTaskWithResumeData: completionHandler:
并創(chuàng)建一個新的下載任務繼續(xù)執(zhí)行之前的下載。
- (void)URLSession:(NSURLSession *)sessiona
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error {
if (error) {
if ([error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData]) {
//self.resumeData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData];//做到這步
//重新請求
NSURLSessionTask *task = [[self backgroundURLSession] downloadTaskWithResumeData: [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData]];
[task resume];
}
}
}
一旦任務失敗就馬上重新請求,不過調(diào)用cancelByProducingResumeData:
方法會觸發(fā)URLSession: task:didCompleteWithError:
(error有值)绘证,所以就做到把resumeData保存下來隧膏。
NSURLSession發(fā)起任務并且對任務強引用
所有的任務都是由session發(fā)起的,任務一旦發(fā)起嚷那,session就會對任務進行強引用胞枕。一旦任務被取消,session就不再對任務進行強引用魏宽,arc中如果沒有對象對某一個對象強引用腐泻,就會被立即釋放。因此任務(task)就會被立即釋放
如果是@property (nonatomic , strong) NSURLSessionDownloadTask *downloadTask;
队询,在“暫团勺”中self.downloadTask = nil;
這句也可以不寫。
NSURLSession 的代理工作隊列
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
NSURLSession 在異步處理上要比NSURLConnection要好蚌斩。
- 下載本身是異步的铆惑,是NSURLSession統(tǒng)一調(diào)度的。(在界面上放置一個uitextview送膳,當下載任務開始的時候拖拽textview,下載任務并不會受到影響)(NSURLConnection在初始化時確定發(fā)送的是同步還是異步請求员魏,而NSURLSession智能異步發(fā)送網(wǎng)絡請求)
- 代理方法的工作隊列指的是:當網(wǎng)絡事件需要監(jiān)聽的時候,去執(zhí)行代理方法所調(diào)度的隊列
-
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
參數(shù)delegateQueue:指定調(diào)度代理方法執(zhí)行的隊列叠聋,并不會影響到session本身的異步執(zhí)行
nil :代理在異步多個線程執(zhí)行
[[NSOperationQueue alloc]init]和nil的執(zhí)行效果一樣撕阎,如果希望代理在異步執(zhí)行,直接使用nil即可
[NSOperationQueue mainQueue]:主隊列 - 如果使用block回調(diào)的方式晒奕,回調(diào)的執(zhí)行線程是異步的闻书。
NSURLSession:session對象會對代理強引用
The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you do not invalidate the session by calling the invalidateAndCancel or finishTasksAndInvalidate method, your app leaks memory until it exits.
session會對代理進行強引用,如果任務執(zhí)行結(jié)束后不取消session,會造成內(nèi)存泄漏脑慧。
使用代理魄眉,一般委托方對代理方弱引用。一旦委托方對代理方強引用闷袒,則會產(chǎn)生循環(huán)引用:
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
這里session對象是委托方坑律,viewController是session的代理,session對vc強引用囊骤。而通常navgationVC導航控制器也會對VC進行強引用晃择。在這種情況下nav對vc pop,vc并不會被釋放掉(點擊開始下載文件也物,無論文件有沒有下載完宫屠,點擊導航欄返回按鈕vc都無法跳進dealloc)(session也不會被取消,下載中的任務仍然繼續(xù))
- (void)dealloc{
NSLog(@"銷毀了");
}
取消會話對象的位置:
1.下載完成時
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{
NSLog(@"finish %@",location);
//完成任務,如果會話已經(jīng)被設置成完成滑蚯,就無法再次使用session(就是說點開始下載浪蹂,完成之后再點一次開始抵栈,此時無法下載)
[self.session finishTasksAndInvalidate];//點擊導航欄返回按鈕可以跳進dealloc,打印出“銷毀了”
//解決辦法坤次,清空session(這樣就可以懶加載了)
self.session = nil;
}
2.視圖控制器銷毀前
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
//取消會話
[self.session invalidateAndCancel];
self.session = nil;
}
當不再需要連接時古劲,可以調(diào)用Session的invalidateAndCancel直接關(guān)閉,或者調(diào)用finishTasksAndInvalidate等待當前Task結(jié)束后關(guān)閉缰猴。這時Delegate會收到URLSession:didBecomeInvalidWithError:這個事件产艾。Delegate收到這個事件之后會被解引用。
兩種方式對比:
方法1:可以保證文件”可能“完整被下載完滑绒,但這種方法會重復創(chuàng)建和銷毀session闷堡,會造成額外的開銷
方法2:只是在離開界面前銷毀sission,相對開銷會小蹬挤。缺點:如果一個文件沒有下載完成會直接被取消掉
真正的解決方法:
在網(wǎng)絡訪問中缚窿,應該將所有的網(wǎng)絡訪問操作棘幸,封裝到一個方法中焰扳。由一個統(tǒng)一的單例來負責處理所有的網(wǎng)絡事件。session對代理進行強引用误续,而單例本身就是一個靜態(tài)實例吨悍,本身就不需要被釋放。
更新:
正在下載時關(guān)掉強制退出程序再重啟的斷點續(xù)傳
得益于這些文章蹋嵌,從它們那學到了很多:
http://www.cocoachina.com/ios/20160503/16053.html
http://www.reibang.com/p/1211cf99dfc3
http://www.cocoachina.com/industry/20131106/7304.html
第一種方法
基本上和之前講的差不多育瓜,但是用到NSURLSession后臺模式。
利用在NSURLSessionConfiguration設置的identifier栽烂。
在應用被殺掉前躏仇,iOS系統(tǒng)保存應用下載session的信息。重新啟動應用腺办,當identifier相同的時候(蘋果通過identifier找到對應的session數(shù)據(jù))焰手,一旦生成Session對象并設置Delegate(否則會因為沒有對 session 的 delegate 進行設定,相應的delegate方法不會被調(diào)用)怀喉,iOS系統(tǒng)會對之前下載中的任務進行依次回調(diào)URLSession:task:didCompleteWithError:方法书妻。
但是當ID不相同,這些情況就收不到了躬拢。因此為了不讓自己的消息被別的應用程序收到躲履,或者收到別的應用程序的消息,起見ID還是和程序的Bundle名稱綁定上比較好聊闯,至少保證唯一性工猜。
demo 還沒有下載完的時候關(guān)掉強制退出程序再重啟,點擊繼續(xù)按鈕進行續(xù)傳菱蔬。
ps:如果用戶先暫停下載篷帅、退出程序,再重啟,那么利用在NSURLSessionConfiguration設置ID這種方法進行續(xù)傳就不可行了犹褒。下面第二種方法就沒有這個問題了抵窒。
第二種方法
在后臺下載模式下,
當使用NSURLSessionDownloadTask進行下載的時候叠骑,系統(tǒng)會在cache文件夾下創(chuàng)建一個下載的路徑(/Downloads)李皇,路徑下會有一個以"CFNetworking"打頭的.tmp文件,這個就是我們正在下載中的文件。而當我們調(diào)用了cancelByProducingResumeData:方法后宙枷,會得到resumeData掉房。而原本存在于Downloads文件下的.tmp文件,則被移動到了tmp文件夾目錄下慰丛。當我們再次進行resume操作的時候卓囚,下載文件則又被移回到了Downloads文件夾下。
暫停時得到的resumeData與.tmp文件是一一對應的蝇棉。DownloadTask進行斷點續(xù)傳的時候,會根據(jù)resumeData中的temp文件名去尋找.tmp文件,然后校驗后再根據(jù)"Range"屬性去進行斷點續(xù)傳。
因此芥永,程序被殺死的斷點下載具體實現(xiàn)思路:
0.選擇document文件夾作為安全目錄篡殷。
1.暫停下載時先清掉document文件夾中的.tmp文件;然后把tmp文件夾中的.tmp文件復制到document文件夾埋涧。并且把resumeData另外以文件的形式保存下來板辽。
2.resumeDownload。先清掉tmp文件夾下的.tmp文件棘催;然后把document中的.tmp文件復制到tmp文件夾劲弦。然后利用保存下來的resumeData對downloadTask resume。
3.(關(guān)鍵)設置一個Bool變量用來判斷是否正在下載中,同時用一個周期事件每隔一段時間暫停一次巧鸭。保存進度步驟同1瓶您。
- (void)download{
//如果設置保存間隔過長,中間殺掉進程可能會損失較多進度
_timer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(pauseToStoreData) userInfo:nil repeats:YES];
NSString *downloadURLString = @"http://sw.bos.baidu.com/sw-search-sp/software/797b4439e2551/QQ_mac_5.0.2.dmg";
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:downloadURLString]];
self.resumeData = [NSData dataWithContentsOfFile:self.resumeDataPath];
if (self.resumeData) {
NSArray *paths = [self.fileManager subpathsAtPath:self.docPath];//查找給定路徑下的所有子路徑.深度查找纲仍,不限于當前層
for (NSString *filePath in paths){
if ([filePath rangeOfString:@"CFNetworkDownload"].length>0)
{
//1.先清掉tmp文件夾下的tmp文件 -removeItemAtPath:目標目錄是文件
[self.fileManager removeItemAtPath:[self.tmpPath stringByAppendingPathComponent:filePath] error:nil];
//2.把doucment中的tmp文件復制到tmp文件夾
self.docTmpFilePath = [_docPath stringByAppendingPathComponent:filePath];//document文件夾中tmp文件的路徑
//-copyItemAtPath:toPath:error:拷貝到目標目錄的時候呀袱,如果文件已經(jīng)存在則會直接失敗;目標目錄必須是文件(一定要以文件名結(jié)尾,而不要以文件夾結(jié)尾)
[self.fileManager copyItemAtPath:_docTmpFilePath toPath:[self.tmpPath stringByAppendingPathComponent:filePath] error:nil];
}
}
self.task = [self.backgroundSession downloadTaskWithResumeData:self.resumeData];
self.resumeData = nil;
}else{
self.task = [self.backgroundSession downloadTaskWithRequest:request];
}
[self.task resume];
}
- (void)pauseDownload{
__weak typeof(self) ws = self;
[self.task cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
ws.task = nil;
ws.resumeData = resumeData;
[resumeData writeToFile:self.resumeDataPath atomically:YES];
NSArray *paths = [self.fileManager subpathsAtPath:self.tmpPath];
for (NSString *filePath in paths)
{
if ([filePath rangeOfString:@"CFNetworkDownload"].length>0)
{
//1.先清掉document文件夾中的tmp文件
[self.fileManager removeItemAtPath:[self.docPath stringByAppendingPathComponent:filePath] error:nil];
//2.把tmp文件夾中的tmp文件復制到document文件夾
self.docTmpFilePath = [self.docPath stringByAppendingPathComponent:filePath];
NSString *path = [self.tmpPath stringByAppendingPathComponent:filePath];
[self.fileManager copyItemAtPath:path toPath:_docTmpFilePath error:nil];
}
}
}];
}
- (void)pauseToStoreData
{
if (!_downloading)
{
return;
}
[_task cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
self.resumeData = resumeData;
self.task = nil;
[resumeData writeToFile:self.resumeDataPath atomically:YES];
NSArray *paths = [self.fileManager subpathsAtPath:self.tmpPath];
for (NSString *filePath in paths)
{
if ([filePath rangeOfString:@"CFNetworkDownload"].length>0)
{
//1.先清掉document文件夾中的tmp文件
[self.fileManager removeItemAtPath:[self.docPath stringByAppendingPathComponent:filePath] error:nil];
//2.把tmp文件夾中的tmp文件復制到document文件夾
self.docTmpFilePath = [self.docPath stringByAppendingPathComponent:filePath];
NSString *path = [self.tmpPath stringByAppendingPathComponent:filePath];
[self.fileManager copyItemAtPath:path toPath:_docTmpFilePath error:nil];
}
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (self.resumeData)
{
self.task = [self.backgroundSession downloadTaskWithResumeData:self.resumeData];
[self.task resume];
}
});
}];
}
因為每隔一段時間暫停一次保存進度郑叠,在實際運行中發(fā)現(xiàn)此處會有明顯的停頓感夜赵。設置的周期間隔過長過短都不好,過短會影響效率乡革,過長則有可能在突然殺掉進程時來不及保存進度導致進度丟失過多寇僧。完整demo
- 留坑待填:
1.防止重復下載摊腋。“當下載的長度等于服務器響應的長度時說明下載過了嘁傀⌒苏簦”