IOS7蘋果添加了NSURLSession后為斷點(diǎn)下載提供了很大的支持。在NSURLConnection 時(shí)期,斷點(diǎn)下載需要做很多工作,包括文件流寫入,獲取文件大小上傳給服務(wù)器對應(yīng)的range,進(jìn)程退到后臺后下載繼續(xù)執(zhí)行等等,也許蘋果考慮到了這些,所以在NSURLSession專門為這些需求提供了支持。
本篇文章主要介紹NSURLSession對于下載的支持和Realm存儲簡介,隨后會貼出NSURLConnection的斷點(diǎn)下載方式,不作具體解釋。開搞?。?/p>
首先NSURLSession 所有的功能都是基于任務(wù)即task,每個(gè)請求都會有一個(gè)task來管理。NSURLSession 為我們提供了三種task:
NSURLSessionDownloadTask,NSURLSessionUploadTask,NSURLSessionDataTask .NSURLSessionDownloadTask 是專門為下載提供的,本篇也是著重使用NSURLSessionDownloadTask,當(dāng)然NSURLSessionDataTask也可以做斷點(diǎn)下載,方式和NSURLConnection類似。
首先NSURLSessionDownloadTask 需要對應(yīng)的一個(gè)代理NSURLSessionDownloadDelegate包含兩個(gè)方法如下
@protocol NSURLSessionDownloadDelegate<NSURLSessionTaskDelegate>
/*
下完完成后會調(diào)用你這個(gè)方法,location即為系統(tǒng)為我們保存的文件地址,此
時(shí)只要將文件移動到自定義的文件夾即可。location的地址打印一下可知是沙
盒目錄“Library/Caches/com.apple.nsurlsessiond/Downloads/cn.gr.LFDownloadDemo/CFNetworkDownload_Y0yyNr.tmp”
*/
- (void)URLSession:(NSURLSession*)session downloadTask:(NSURLSessionDownloadTask*)downloadTask
didFinishDownloadingToURL:(NSURL*)location;
@optional
/* 下載過程中會不斷調(diào)用這個(gè)方法,我們可以獲取文件大小和下載進(jìn)度,[demo](https://github.com/wlfiou/LFDownLoadManager)中通過通知將過程通知顯示層 */
- (void)URLSession:(NSURLSession*)session downloadTask:(NSURLSessionDownloadTask*)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
實(shí)現(xiàn)斷點(diǎn)下載,主要依賴于下面的方法
/* 通過resumeData來記錄已下載文件的情況,可通過序列化打印出具體內(nèi)容。這里需要解釋一下,可能剛接觸的同學(xué)會把resumeData 理解為已下載的文件,這里并不是 。resumeData只是記錄已下載的文件的情況,而已下載的文件,NSURLSession為我們存到了,temp文件中,不用我們來處理。*/
- (NSURLSessionDownloadTask*)downloadTaskWithResumeData:(NSData*)resumeData;
怎么拿到上述的resumeData呢,demo的思路是這樣的
情況一.進(jìn)程沒有被退出,只是點(diǎn)擊了暫停,這時(shí)會調(diào)用
- (void)cancelByProducingResumeData:(void(^)(NSData*_Nullable resumeData))completionHandler;
可見block的參數(shù)即為我們需要的,只要保存resumeData,下次點(diǎn)擊繼續(xù)的時(shí)候用resumeData新建任務(wù)即可繼續(xù)上次下載,
情況二.進(jìn)程在下載的時(shí)候被退出,此時(shí)需要我們在AppDelegate做一些處理如下代碼
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// 實(shí)現(xiàn)如下代碼,才能使程序處于后臺時(shí)被殺死,調(diào)用applicationWillTerminate:方法
[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(){}];
}
這樣在退出的時(shí)候會調(diào)用下面的方法
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
if (error && [error.localizedDescription isEqualToString:@"cancelled"]) {
return;
}
LFDownLoadModel *model = [[LFDownLoadDatabaseManager shareManager] getModelWithUrl:task.taskDescription];
// 下載時(shí),進(jìn)程殺死,重新啟動,回調(diào)錯(cuò)誤
if (error && [error.userInfo objectForKey:NSURLErrorBackgroundTaskCancelledReasonKey]) {
[[LFDownLoadDatabaseManager shareManager] transactionWithBlock:^{
model.state = LFDownloadStateWaiting;
}];
model.resumeData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData];
[model writeDataToLocalPath:model.resumeData];
return ;
}
if (error) {
[[LFDownLoadDatabaseManager shareManager] transactionWithBlock:^{
model.state = LFDownloadStateError;
}];
model.resumeData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData];
[model writeDataToLocalPath:model.resumeData];
return ;
}else{
[[LFDownLoadDatabaseManager shareManager] transactionWithBlock:^{
model.state = LFDownloadStateFinish;
}];
}
if (_currentCount) {
_currentCount--;
[self.dataTaskDic removeObjectForKey:model.url];
}
[self startDownloadWaitingTask];
NSLog(@"\n 文件:%@,下載完成 \n 本地路徑:%@ \n 錯(cuò)誤:%@ \n", model.fileName, model.localPath, error);
}
這樣可以拿到此時(shí)的resumeData,demo中保存到了本地,再次打開程序時(shí),點(diǎn)擊開始,會從本地獲取已經(jīng)存取好的data 按照上述步驟構(gòu)建task即可。
Realm
demo存儲數(shù)據(jù)選擇的是Reaml,不太了解Realm 的同學(xué),也可以參照demo 的用法進(jìn)行簡單的了解。
這里簡單介紹一下,Realm的使用方法。
首先Realm的初始化方法有兩種:
一.使用默認(rèn)的初始化如下
使用該方法的話,資源存儲位置為默認(rèn)的Documents下面的default.realm
+ (instancetype)defaultRealm;
使用的時(shí)候只需調(diào)用 [RLMRealm defaultRealm]即可,不需要自己再設(shè)計(jì)單例
二.自定義Configuration (demo采用此種方式)
詳情可見LFDataBase文件
+(RLMRealmConfiguration *)config{
static RLMRealmConfiguration *_config ;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_config = [[RLMRealmConfiguration alloc]init];
NSString *path = [LFUtil DocumentDirectory];
_config.deleteRealmIfMigrationNeeded = YES;
NSString *loadPath = [path stringByAppendingPathComponent:@"LFDownload"];
BOOL isRE = [[NSFileManager defaultManager] fileExistsAtPath:loadPath];
if (!isRE) {
[[NSFileManager defaultManager] createDirectoryAtPath:loadPath withIntermediateDirectories:YES attributes:nil error:nil];
}
NSString *downloadDB = [loadPath stringByAppendingPathComponent:@"downloadDB.realm"];
_config.fileURL = [NSURL URLWithString:downloadDB];
});
return _config ;
}
+(RLMRealm *)db{
RLMRealm *realm = [RLMRealm realmWithConfiguration:self.config error:nil];
return realm;
}
Realm不可跨線程使用資源,即單線程查出來的資源,需要做一下轉(zhuǎn)換才能使用,demo里面做了一下copy生成新的model進(jìn)行操作詳情可見LFDownLoadModel。Realm中每個(gè)Model都是一個(gè)表單,Model繼承RLMObject即可,同時(shí)需要存儲的字段不再需要修飾詞修飾Realm會為我們管理。
Realm摒棄了復(fù)雜的sql語句,只需要像平時(shí)使用謂詞那樣,進(jìn)行查找如下代碼
NSPredicate *pred = [NSPredicate predicateWithFormat:@"state = %d",LFDownloadStateWaiting];
results = [[LFDownLoadModel objectsInRealm:real withPredicate:pred ] sortedResultsUsingKeyPath:@"lastStateTime" ascending:YES];//遞增
修改也很方便,在事務(wù)中執(zhí)行賦值即可修改如下代碼
[[LFDownLoadDatabaseManager shareManager] transactionWithBlock:^{
model.state = LFDownloadStateFinish;
}];
需要注意的是,Realm存儲數(shù)據(jù)的大小最高為16M,所以資源最好存在文件中,Realm只存儲地址就好。
Realm的高效體現(xiàn)在大量數(shù)據(jù)操作的時(shí)候,相比于sqllite 效率提升的很多,而且比coreData更加輕量易用,demo只是簡單使用,有興趣同學(xué)可以深入研究。demo