1. 程式人生 > >iOS開發之網路程式設計--4、NSURLSessionDataTask實現檔案下載(離線斷點續傳下載)

iOS開發之網路程式設計--4、NSURLSessionDataTask實現檔案下載(離線斷點續傳下載)

前言:根據前篇《iOS開發之網路程式設計--2、NSURLSessionDownloadTask檔案下載》或者《iOS開發之網路程式設計--3、NSURLSessionDataTask實現檔案下載(離線斷點續傳下載)》,都遺留了一個細節未處理的問題,那就是在離線斷點下載的過程中,當應用程式重新啟動之後,進度條的進度值預設沒有設定為之前已經下載的進度,根據基本公式"當前進度值 = 已經下載的資料長度 ÷ 最終下載完的資料總長度",已經下載的資料長度可以由沙盒中已經下載的那部分資料獲取,但是最終下載完的資料總長度就需要通過網路返回的資訊了,但是別忘了,每一次重新啟動應用程式初始狀態預設都是暫停下載,或者是斷網的情況下無法請求網路資料,那麼如何獲取這個"最終下載完的資料總長度"呢?

本篇還涉及到在子執行緒建立下載任務,然後通過執行緒通知UI主執行緒更新進度條控制元件顯示進度。因為delegateQueue這個屬性可以設定主佇列執行緒或者是子佇列執行緒。

先看看效果:

問題解決:"最終下載完的資料總長度"可以在首次從0開始下載的時候通過網路獲取,然後將其"最終下載完的資料總長度"這個值儲存在快取中的某個檔案(這個檔案可以是字典等等)中,等待下一次獲取。

       而我則採用的方法是將這個"最終下載完的資料總長度"作為檔案的屬性新增進檔案屬性列表中,以備下一次讀取的時候,獲得到這個檔案之後,就可以讀取該檔案的屬性列表中的"最終下載完

     的資料總長度"的屬性和屬性值。

在這裡不得不先介紹一個工具類,讀者可以通過本人的另一篇博文隨筆先了解其功能:iOS開發 -- 為本地檔案新增自定義屬性的工具類

為本地檔案新增屬性之後,可以列印看的到:

本人花了點時間將網路下載這部分簡單的封裝成了一個工具類:DownloadTool,下面就展示原始碼:

DownloadTool.h

複製程式碼
 1 #import <Foundation/Foundation.h>
 2 
 3 
 4 // 定義一個block用來傳遞進度值
 5 typedef  void (^SetProgressValue)(float progressValue);
 6 
 7 @interface
DownloadTool : NSObject 8 9 /** 建立下載工具物件 */ 10 + (instancetype)DownloadWithURLString:(NSString*)urlString setProgressValue:(SetProgressValue)setProgressValue; 11 /** 開始下載 */ 12 -(void)startDownload; 13 /** 暫停下載 */ 14 -(void)suspendDownload; 15 16 @end
複製程式碼

DownloadTool.m

複製程式碼
  1 #import "DownloadTool.h"
  2 
  3 #import "ExpendFileAttributes.h"
  4 
  5 #define Key_FileTotalSize @"Key_FileTotalSize"
  6 
  7 @interface DownloadTool () <NSURLSessionDataDelegate>
  8 /** Session會話 */
  9 @property (nonatomic,strong)NSURLSession *session;
 10 /** Task任務 */
 11 @property (nonatomic,strong)NSURLSessionDataTask *task;
 12 /** 檔案的全路徑 */
 13 @property (nonatomic,strong)NSString *fileFullPath;
 14 /** 傳遞進度值的block */
 15 @property (nonatomic,copy) SetProgressValue setProgressValue;
 16 /** 當前已經下載的檔案的長度 */
 17 @property (nonatomic,assign)NSInteger currentFileSize;
 18 /** 輸出流 */
 19 @property (nonatomic,strong)NSOutputStream *outputStream;
 20 /** 不變的檔案總長度 */
 21 @property (nonatomic,assign)NSInteger fileTotalSize;
 22 @end
 23 
 24 @implementation DownloadTool
 25 
 26 + (instancetype)DownloadWithURLString:(NSString*)urlString setProgressValue:(SetProgressValue)setProgressValue{
 27     DownloadTool* download = [[DownloadTool alloc] init];
 28     download.setProgressValue = setProgressValue;
 29     [download getFileSizeWithURLString:urlString];
 30     [download creatDownloadSessionTaskWithURLString:urlString];
 31     NSLog(@"%@",download.fileFullPath);
 32     return download;
 33 }
 34 // 剛建立該網路下載工具類的時候,就需要查詢本地是否有已經下載的檔案,並返回該檔案已經下載的長度
 35 -(void)getFileSizeWithURLString:(NSString*)urlString{
 36     // 建立檔案管理者
 37     NSFileManager* fileManager = [NSFileManager defaultManager];
 38     // 獲取檔案各個部分
 39     NSArray* fileComponents = [fileManager componentsToDisplayForPath:urlString];
 40     // 獲取下載之後的檔名
 41     NSString* fileName = [fileComponents lastObject];
 42     // 根據檔名拼接沙盒全路徑
 43     NSString* fileFullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:fileName];
 44     self.fileFullPath = fileFullPath;
 45     
 46     NSDictionary* attributes = [fileManager attributesOfItemAtPath:fileFullPath
 47                                                              error:nil];
 48     // 如果有該檔案,且為下載沒完成,就直接拿出該檔案的長度設定進度值,並設定當前的檔案長度
 49     NSInteger fileCurrentSize = [attributes[@"NSFileSize"] integerValue];
 50     // 如果檔案長度為0,就不需要計算進度值了
 51     if (fileCurrentSize != 0) {
 52         // 獲取最終的檔案中長度
 53         NSInteger fileTotalSize = [[ExpendFileAttributes stringValueWithPath:self.fileFullPath key:Key_FileTotalSize] integerValue];
 54         self.currentFileSize = fileCurrentSize;
 55         self.fileTotalSize = fileTotalSize;
 56         // 設定進度條的值
 57         self.setProgressValue(1.0 * fileCurrentSize / fileTotalSize);
 58     }
 59     NSLog(@"當前檔案長度:%lf" , self.currentFileSize * 1.0);
 60 }
 61 #pragma mark - 建立網路請求會話和任務,並啟動任務
 62 -(void)creatDownloadSessionTaskWithURLString:(NSString*)urlString{
 63     //判斷檔案是否已經下載完畢
 64     if (self.currentFileSize == self.fileTotalSize && self.currentFileSize != 0) {
 65         NSLog(@"檔案已經下載完畢");
 66         return;
 67     }
 68     NSURLSession* session =
 69     [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
 70                                   delegate:self
 71                              delegateQueue:[[NSOperationQueue alloc]init]];
 72     NSURL* url = [NSURL URLWithString:urlString];
 73     NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url];
 74     //2.3 設定請求頭
 75     NSString *range = [NSString stringWithFormat:@"bytes=%zd-",self.currentFileSize];
 76     [request setValue:range forHTTPHeaderField:@"Range"];
 77     NSURLSessionDataTask* task = [session dataTaskWithRequest:request];
 78     self.session = session;
 79     self.task = task;
 80 }
 81 
 82 #pragma mark - 控制下載的狀態
 83 // 開始下載
 84 -(void)startDownload{
 85     [self.task resume];
 86 }
 87 // 暫停下載
 88 -(void)suspendDownload{
 89     [self.task suspend];
 90 }
 91 #pragma mark - NSURLSessionDataDelegate 的代理方法
 92 // 收到響應呼叫的代理方法
 93 -(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:
 94 (NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{
 95     NSLog(@"執行了收到響應呼叫的代理方法");
 96     // 建立輸出流,並開啟流
 97     NSOutputStream* outputStream = [[NSOutputStream alloc] initToFileAtPath:self.fileFullPath append:YES];
 98     [outputStream open];
 99     self.outputStream = outputStream;
100     // 如果當前已經下載的檔案長度等於0,那麼就需要將總長度資訊寫入檔案中
101     if (self.currentFileSize == 0) {
102         NSInteger totalSize = response.expectedContentLength;
103         NSString* totalSizeString = [NSString stringWithFormat:@"%ld",totalSize];
104         [ExpendFileAttributes extendedStringValueWithPath:self.fileFullPath key:Key_FileTotalSize value:totalSizeString];
105         // 別忘了設定總長度
106         self.fileTotalSize = totalSize;
107     }
108     // 允許收到響應
109     completionHandler(NSURLSessionResponseAllow);
110 }
111 // 收到資料呼叫的代理方法
112 -(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
113     NSLog(@"執行了收到資料呼叫的代理方法");
114     // 通過輸出流寫入資料
115     [self.outputStream write:data.bytes maxLength:data.length];
116     // 將寫入的資料的長度計算加進當前的已經下載的資料長度
117     self.currentFileSize += data.length;
118     // 設定進度值
119     NSLog(@"當前檔案長度:%lf,總長度:%lf",self.currentFileSize * 1.0,self.fileTotalSize * 1.0);
120     NSLog(@"進度值: %lf",self.currentFileSize * 1.0 / self.fileTotalSize);
121     // 獲取主執行緒
122     NSOperationQueue* mainQueue = [NSOperationQueue mainQueue];
123     [mainQueue addOperationWithBlock:^{
124         self.setProgressValue(self.currentFileSize * 1.0 / self.fileTotalSize);
125     }];
126 }
127 // 資料下載完成呼叫的方法
128 -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
129     // 關閉輸出流 並關閉強指標
130     [self.outputStream close];
131     self.outputStream = nil;
132     // 關閉會話
133     [self.session invalidateAndCancel];
134     NSLog(@"%@",[NSThread currentThread]);
135 }
136 -(void)dealloc{
137 }
138 @end
複製程式碼

使用示例原始碼:

複製程式碼
 1 #import "ViewController.h"
 2 #import "RainbowProgress.h"
 3 
 4 #import "DownloadTool.h"
 5 
 6 #define MP4_URL_String @"http://120.25.226.186:32812/resources/videos/minion_12.mp4"
 7 
 8 
 9 @interface ViewController ()
10 @property (weak, nonatomic) IBOutlet UILabel *showDownloadState;
11 /** 彩虹進度條 */
12 @property (nonatomic,weak)RainbowProgress *rainbowProgress;
13 /** 網路下載工具物件 */
14 @property (nonatomic,strong)DownloadTool *download;
15 @end
16 
17 @implementation ViewController
18 
19 - (void)viewDidLoad {
20     [super viewDidLoad];
21     [self setSelfView];
22     [self addProgress];
23     [self addDownload];
24     
25 }
26 // 啟動和關閉的網路下載開關
27 - (IBAction)SwitchBtn:(UISwitch *)sender {
28     if (sender.isOn) {
29         self.showDownloadState.text = @"開始下載";
30         [self.download startDownload];
31     }else{
32         self.showDownloadState.text = @"暫停下載";
33         [self.download suspendDownload];
34     }
35 }
36 #pragma mark - 設定控制器View
37 -(void)setSelfView{
38     self.view.backgroundColor = [UIColor blackColor];
39 }
40 #pragma mark - 新增彩虹進度條
41 -(void)addProgress{
42     // 建立彩虹進度條,並啟動動畫
43     RainbowProgress* rainbowProgress = [[RainbowProgress alloc] init];
44     [rainbowProgress startAnimating];
45     [self.view addSubview:rainbowProgress];
46     self.rainbowProgress = rainbowProgress;
47 }
48 #pragma mark - 建立網路下載任務
49 -(void)addDownload{
50     DownloadTool* download = [DownloadTool DownloadWithURLString:MP4_URL_String setProgressValue:^(float progressValue) {
51         self.rainbowProgress.progressValue = progressValue;
52     }];
53     self.download = download;
54 }
55 
56 #pragma mark - 設定狀態列樣式
57 -(UIStatusBarStyle)preferredStatusBarStyle{
58     return UIStatusBarStyleLightContent;
59 }
60 
61 @end
複製程式碼

效果圖(中間有個過程是重新啟動應用程式看看進度條顯示的效果,然後繼續測試開始下載和暫停下載):