iOS網絡-04-大文件下載

大文件下載注意事項


  • 若不對下載的文件進行轉存,會造成內存消耗急劇升高,甚至耗盡內存資源,造成程序終止。
  • 在文件下載過程中通常會出現中途停止的狀況,若不做處理,就要重新開始下載,浪費流量。

大文件下載的解決方案


  • 對下載文件進行處理,每下載一點數據,就將數據寫到磁盤中(通常是沙盒中),避免在內存累積數據(NSURLConnection下載)
    • 使用NSFileHandle類實現寫數據
    • 使用NSOutputStream類實現寫數據
  • 當下載任務終止時,記錄任務終止時的位置信息,以便下次開始繼續下載

大文件下載(NSURLConnection)


  • 未支持斷點下載
  • 使用NSURLConnection的代理方式下載文件
  • 在下載任務的不同階段回調的代理方法中,完成轉移下載文件,及記錄終止位置的任務
  • 使用NSFileHandle類實現寫數據的下載步驟(完整核心代碼)
    • 設置相關成員屬性

      /**所要下載文件的總長度*/
      @property (nonatomic, assign) NSInteger contentLength;
      /**已下載文件的總長度*/
      @property (nonatomic, assign) NSInteger currentLength
      /**文件句柄,用來實現文件存儲*/
      @property (nonatomic, strong) NSFileHandle *handle;
    • 創建、發送請求

      // 1. 創建請求路徑
      NSURL *url = [NSURL URLWithString:@"此處爲URL字符串"];
      // 2. 將URL封裝成請求
      NSURLRequest *request = [NSURLRequest requestWithURL:url];
      // 3. 通過NSURLConnection,並設置代理
      [NSURLConnection connectionWithRequest:request delegate:self];
    • 遵守代理協議NSURLConnectionDataDelegate,實現代理方法

      /**
      * 接收到服務器響應時調用的方法
      */
      - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSHTTPURLResponse *)response
      {
          //獲取所要下載文件的總長度
          self.contentLength = [response.allHeaderFields[@"Content-Length"] integerValue];
          //拼接一個沙盒中的文件路徑
          NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"minion_15.mp4"];
          //創建指定路徑的文件
          [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
          //創建文件句柄
          self.handle = [NSFileHandle fileHandleForWritingAtPath:filePath];
      }
      /**
      * 接收到服務器的數據時調用的方法
      */
      - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
      {
          //定位到文件尾部,將服務器每次返回的文件數據都拼接到文件尾部
          [self.handle seekToEndOfFile];
          //通過文件句柄,將文件寫入到沙盒中
          [self.handle writeData:data];
          //拼接已下載文件總長度
          self.currentLength += data.length;
          //計算下載進度
          CGFloat progress = 1.0 * self.currentLength / self.contentLength;
      }
      /**
      * 文件下載完畢時調用的方法
      */
      - (void)connectionDidFinishLoading:(NSURLConnection *)connection
      {
          //關閉文件句柄,並清除
          [self.handle closeFile];
          self.handle = nil;
          //清空已下載文件長度
          self.currentLength = 0;
      }
  • 使用NSOutputStream類實現寫數據的下載步驟(部分代碼,其他部分代碼同上
    • 設置NSOutputStream成員屬性

      @property (nonatomic, strong) NSOutputStream *stream;
    • 初始化NSOutputStream對象,打開輸出流

      /**接收到服務器響應的時候調用*/
      - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
      {
          //獲取下載數據保存的路徑
          NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
          NSString *filePath = [cache stringByAppendingPathComponent:response.suggestedFilename];
          //利用NSOutputStream往filePath文件中寫數據,若append參數爲yes,則會寫到文件尾部
          self.stream = [[NSOutputStream alloc] initToFileAtPath:filePath append:YES];
          //打開數據流
          [self.stream open];
      }
    • 寫文件數據

      /**接收到數據的時候調用*/
      - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
      {
          [self.stream write:[data bytes] maxLength:data.length];
      }
    • 關閉輸出流

      /**數據下載完畢的時候調用*/
      - (void)connectionDidFinishLoading:(NSURLConnection *)connection
      {
          [self.stream close];
      }

大文件下載(NSURLSession)


  • 支持斷點下載,自動記錄停止下載時斷點的位置
  • 遵守NSURLSessionDownloadDelegate協議
  • 使用NSURLSession下載大文件,被下載文件會被自動寫入沙盒的臨時文件夾tmp中
  • 下載完畢,通常需要將已下載文件移動其他位置(tmp文件夾中的數據被定時刪除),通常是cache文件夾中
  • 詳細的下載步驟
    • 設置下載任務task的爲成員變量

      @property (nonatomic, strong) NSURLSessionDownloadTask *task;
    • 獲取NSURLSession對象

      NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
    • 初始化下載任務任務

      self.task = [session downloadTaskWithURL:(此處爲下載文件路徑URL)];
    • 實現代理方法

      /**每當寫入數據到臨時文件的時候,就會調用一次該方法,通常在該方法中獲取下載進度*/
      - (void)URLSession:(NSURLSession *)session downloadTask: (NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
      {
          // 計算下載進度
          CGFloat progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite;
      }
      
      /**任務終止時調用的方法,通常用於斷點下載*/
      - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
      {
          //fileOffset:下載任務中止時的偏移量
      }
      
      /**遇到錯誤的時候調用,error參數只能傳遞客戶端的錯誤*/
      - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
      { }
      
      /**下載完成的時候調用,需要將文件剪切到可以長期保存的文件夾中*/
      - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
      {
          //生成文件長期保存的路徑
          NSString *file = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
          //獲取文件句柄
          NSFileManager *fileManager = [NSFileManager defaultManager];
          //通過文件句柄,將文件剪切到文件長期保存的路徑
          [fileManager moveItemAtURL:location toURL:[NSURL fileURLWithPath:file] error:nil];
      }
    • 操作任務狀態

      /**開始/繼續下載任務*/
      [self.task resume];
      
      /**暫停下載任務*/
      [self.task suspend];
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章