ios斷點續傳:NSURLSession和NSURLSessionDataTask實現

来源:http://www.cnblogs.com/allencelee/archive/2016/08/26/5810819.html
-Advertisement-
Play Games

蘋果提供的NSURLSessionDownloadTask雖然能實現斷點續傳,但是有些情況是無法處理的,比如程式強制退出或沒有調用 cancelByProducingResumeData取消方法,這時就無法斷點續傳了。 使用NSURLSession和NSURLSessionDataTask實現斷點續 ...


蘋果提供的NSURLSessionDownloadTask雖然能實現斷點續傳,但是有些情況是無法處理的,比如程式強制退出或沒有調用

cancelByProducingResumeData取消方法,這時就無法斷點續傳了。

 

使用NSURLSession和NSURLSessionDataTask實現斷點續傳的過程是:

1、配置NSMutableURLRequest對象的Range請求頭欄位信息

2、創建使用代理的NSURLSession對象

3、使用NSURLSession對象和NSMutableURLRequest對象創建NSURLSessionDataTask對象,啟動任務。

4、在NSURLSessionDataDelegate的didReceiveData方法中追加獲取下載數據到目標文件。

 

下麵是具體實現,封裝了一個續傳管理器。可以直接拷貝到你的工程里,也可以參考我提供的DEMO:http://pan.baidu.com/s/1c0BHToW

 

//

//  MQLResumeManager.h

//

//  Created by MQL on 15/10/21.

//  Copyright © 2015年. All rights reserved.

//

 

#import <Foundation/Foundation.h>

 

@interface MQLResumeManager : NSObject

 

/**

 *  創建斷點續傳管理對象,啟動下載請求

 *

 *  @param url          文件資源地址

 *  @param targetPath   文件存放路徑

 *  @param success      文件下載成功的回調塊

 *  @param failure      文件下載失敗的回調塊

 *  @param progress     文件下載進度的回調塊

 *

 *  @return 斷點續傳管理對象

 *

 */

+(MQLResumeManager*)resumeManagerWithURL:(NSURL*)url

                                targetPath:(NSString*)targetPath

                                success:(void (^)())success

                                failure:(void (^)(NSError*error))failure

                               progress:(void (^)(longlongtotalReceivedContentLength,longlong totalContentLength))progress;

 

/**

 *  啟動斷點續傳下載請求

 */

-(void)start;

 

/**

 *  取消斷點續傳下載請求

 */

-(void)cancel;

 

 

@end

 

 

 

  1 //
  2 
  3 //  MQLResumeManager.m
  4 
  5 //
  6 
  7 //  Created by MQL on 15/10/21.
  8 
  9 //  Copyright © 2015年. All rights reserved.
 10 
 11 //
 12 
 13  
 14 
 15 #import "MQLResumeManager.h"
 16 
 17  
 18 
 19 typedef void (^completionBlock)();
 20 
 21 typedef void (^progressBlock)();
 22 
 23  
 24 
 25 @interface MQLResumeManager ()<NSURLSessionDelegate,NSURLSessionTaskDelegate>
 26 
 27  
 28 
 29 @property (nonatomic,strong)NSURLSession *session;   //註意一個session只能有一個請求任務
 30 
 31 @property(nonatomic,readwrite,retain)NSError *error;//請求出錯
 32 
 33 @property(nonatomic,readwrite,copy)completionBlockcompletionBlock;
 34 
 35 @property(nonatomic,readwrite,copy)progressBlock progressBlock;
 36 
 37  
 38 
 39 @property (nonatomic,strong)NSURL *url;          //文件資源地址
 40 
 41 @property (nonatomic,strong)NSString *targetPath;//文件存放路徑
 42 
 43 @property longlong totalContentLength;            //文件總大小
 44 
 45 @property longlong totalReceivedContentLength;    //已下載大小
 46 
 47  
 48 
 49 /**
 50 
 51  *  設置成功、失敗回調block
 52 
 53  *
 54 
 55  *  @param success 成功回調block
 56 
 57  *  @param failure 失敗回調block
 58 
 59  */
 60 
 61 - (void)setCompletionBlockWithSuccess:(void (^)())success
 62 
 63                               failure:(void (^)(NSError*error))failure;
 64 
 65  
 66 
 67 /**
 68 
 69  *  設置進度回調block
 70 
 71  *
 72 
 73  *  @param progress
 74 
 75  */
 76 
 77 -(void)setProgressBlockWithProgress:(void (^)(longlongtotalReceivedContentLength,longlong totalContentLength))progress;
 78 
 79  
 80 
 81 /**
 82 
 83  *  獲取文件大小
 84 
 85  *  @param path 文件路徑
 86 
 87  *  @return 文件大小
 88 
 89  *
 90 
 91  */
 92 
 93 - (long long)fileSizeForPath:(NSString *)path;
 94 
 95  
 96 
 97 @end
 98 
 99  
100 
101 @implementation MQLResumeManager
102 
103  
104 
105 /**
106 
107  *  設置成功、失敗回調block
108 
109  *
110 
111  *  @param success 成功回調block
112 
113  *  @param failure 失敗回調block
114 
115  */
116 
117 - (void)setCompletionBlockWithSuccess:(void (^)())success
118 
119                               failure:(void (^)(NSError*error))failure{
120 
121     
122 
123     __weak typeof(self) weakSelf =self;
124 
125     self.completionBlock = ^ {
126 
127         
128 
129         dispatch_async(dispatch_get_main_queue(), ^{
130 
131             
132 
133             if (weakSelf.error) {
134 
135                 if (failure) {
136 
137                     failure(weakSelf.error);
138 
139                 }
140 
141             } else {
142 
143                 if (success) {
144 
145                     success();
146 
147                 }
148 
149             }
150 
151             
152 
153         });
154 
155     };
156 
157 }
158 
159  
160 
161 /**
162 
163  *  設置進度回調block
164 
165  *
166 
167  *  @param progress
168 
169  */
170 
171 -(void)setProgressBlockWithProgress:(void (^)(longlongtotalReceivedContentLength,longlong totalContentLength))progress{
172 
173     
174 
175     __weak typeof(self) weakSelf =self;
176 
177     self.progressBlock = ^{
178 
179         
180 
181         dispatch_async(dispatch_get_main_queue(), ^{
182 
183             
184 
185             progress(weakSelf.totalReceivedContentLength, weakSelf.totalContentLength);
186 
187         });
188 
189     };
190 
191 }
192 
193  
194 
195 /**
196 
197  *  獲取文件大小
198 
199  *  @param path 文件路徑
200 
201  *  @return 文件大小
202 
203  *
204 
205  */
206 
207 - (long long)fileSizeForPath:(NSString *)path {
208 
209     
210 
211     long long fileSize =0;
212 
213     NSFileManager *fileManager = [NSFileManagernew];// not thread safe
214 
215     if ([fileManager fileExistsAtPath:path]) {
216 
217         NSError *error = nil;
218 
219         NSDictionary *fileDict = [fileManagerattributesOfItemAtPath:path error:&error];
220 
221         if (!error && fileDict) {
222 
223             fileSize = [fileDict fileSize];
224 
225         }
226 
227     }
228 
229     return fileSize;
230 
231 }
232 
233  
234 
235 /**
236 
237  *  創建斷點續傳管理對象,啟動下載請求
238 
239  *
240 
241  *  @param url          文件資源地址
242 
243  *  @param targetPath   文件存放路徑
244 
245  *  @param success      文件下載成功的回調塊
246 
247  *  @param failure      文件下載失敗的回調塊
248 
249  *  @param progress     文件下載進度的回調塊
250 
251  *
252 
253  *  @return 斷點續傳管理對象
254 
255  *
256 
257  */
258 
259 +(MQLResumeManager*)resumeManagerWithURL:(NSURL*)url
260 
261                               targetPath:(NSString*)targetPath
262 
263                                  success:(void (^)())success
264 
265                                  failure:(void (^)(NSError*error))failure
266 
267                                 progress:(void (^)(longlongtotalReceivedContentLength,longlong totalContentLength))progress{
268 
269     
270 
271     MQLResumeManager *manager = [[MQLResumeManageralloc]init];
272 
273     
274 
275     manager.url = url;
276 
277     manager.targetPath = targetPath;
278 
279     [managersetCompletionBlockWithSuccess:successfailure:failure];
280 
281     [manager setProgressBlockWithProgress:progress];
282 
283     
284 
285     manager.totalContentLength =0;
286 
287     manager.totalReceivedContentLength =0;
288 
289     
290 
291     return manager;
292 
293 }
294 
295  
296 
297 /**
298 
299  *  啟動斷點續傳下載請求
300 
301  */
302 
303 -(void)start{
304 
305     
306 
307     NSMutableURLRequest *request = [[NSMutableURLRequestalloc]initWithURL:self.url];
308 
309     
310 
311     longlong downloadedBytes =self.totalReceivedContentLength = [selffileSizeForPath:self.targetPath];
312 
313     if (downloadedBytes > 0) {
314 
315         
316 
317         NSString *requestRange = [NSStringstringWithFormat:@"bytes=%llu-", downloadedBytes];
318 
319         [request setValue:requestRangeforHTTPHeaderField:@"Range"];
320 
321     }else{
322 
323         
324 
325         int fileDescriptor =open([self.targetPathUTF8String],O_CREAT |O_EXCL |O_RDWR,0666);
326 
327         if (fileDescriptor > 0) {
328 
329             close(fileDescriptor);
330 
331         }
332 
333     }
334 
335     
336 
337     NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfigurationdefaultSessionConfiguration];
338 
339     NSOperationQueue *queue = [[NSOperationQueuealloc]init];
340 
341     self.session = [NSURLSessionsessionWithConfiguration:sessionConfigurationdelegate:selfdelegateQueue:queue];
342 
343     
344 
345     NSURLSessionDataTask *dataTask = [self.sessiondataTaskWithRequest:request];
346 
347     [dataTask resume];
348 
349 }
350 
351  
352 
353 /**
354 
355  *  取消斷點續傳下載請求
356 
357  */
358 
359 -(void)cancel{
360 
361     
362 
363     if (self.session) {
364 
365         
366 
367         [self.sessioninvalidateAndCancel];
368 
369         self.session =nil;
370 
371     }
372 
373 }
374 
375  
376 
377 #pragma mark -- NSURLSessionDelegate
378 
379 /* The last message a session delegate receives.  A session will only become
380 
381  * invalid because of a systemic error or when it has been
382 
383  * explicitly invalidated, in which case the error parameter will be nil.
384 
385  */
386 
387 - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullableNSError *)error{
388 
389     
390 
391     NSLog(@"didBecomeInvalidWithError");
392 
393 }
394 
395  
396 
397 #pragma mark -- NSURLSessionTaskDelegate
398 
399 /* Sent as the last message related to a specific task.  Error may be
400 
401  * nil, which implies that no error occurred and this task is complete.
402 
403  */
404 
405 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask*)task
406 
407 didCompleteWithError:(nullable NSError *)error{
408 
409     
410 
411     NSLog(@"didCompleteWithError");
412 
413     
414 
415     if (error == nil &&self.error ==nil) {
416 
417         
418 
419         self.completionBlock();
420 
421         
422 
423     }else if (error !=nil){
424 
425         
426 
427         if (error.code != -999) {
428 
429             
430 
431             self.error = error;
432 
433             self.completionBlock();
434 
435         }
436 
437         
438 
439     }else if (self.error !=nil){
440 
441         
442 
443         self.completionBlock();
444 
445     }
446 
447     
448 
449     
450 
451 }
452 
453  
454 
455 #pragma mark -- NSURLSessionDataDelegate
456 
457 /* Sent when data is available for the delegate to consume.  It is
458 
459  * assumed that the delegate will retain and not copy the data.  As
460 
461  * the data may be discontiguous, you should use
462 
463  * [NSData enumerateByteRangesUsingBlock:] to access it.
464 
465  */
466 
467 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
468 
469     didReceiveData:(NSData *)data{
470 
471     
472 
473     //根據status code的不同,做相應的處理
474 
475     NSHTTPURLResponse *response = (NSHTTPURLResponse*)dataTask.response;
476 
477     if (response.statusCode ==200) {
478 
479         
480 
481         self.totalContentLength = dataTask.countOfBytesExpectedToReceive;
482 
483         
484 
485     }else if (response.statusCode ==206){
486 
487         
488 
489         NSString *contentRange = [response.allHeaderFieldsvalueForKey:@"Content-Range"];
490 
491         if ([contentRange hasPrefix:@"bytes"]) {
492 
493             NSArray *bytes = [contentRangecomponentsSeparatedByCharactersInSet:[NSCharacterSetcharacterSetWithCharactersInString:@" -/"]];
494 
495             if ([bytes count] == 4) {
496 
497                 self.totalContentLength = [[bytesobjectAtIndex:3]longLongValue];
498 
499             }
500 
501         }
502 
503     }else if (response.statusCode ==416){
504 
505         
506 
507         NSString *contentRange = [response.allHeaderFieldsvalueForKey:@"Content-Range"];
508 
509         if ([contentRange hasPrefix:@"bytes"]) {
510 
511             NSArray *bytes = [contentRangecomponentsSeparatedByCharactersInSet:[NSCharacterSetcharacterSetWithCharactersInString:@" -/"]];
512 
513             if ([bytes count] == 3) {
514 
515                 
516 
517                 self.totalContentLength = [[bytesobjectAtIndex:2]longLongValue];
518 
519                 if (self.totalReceivedContentLength==self.totalContentLength) {
520 
521                     
522 
523                     //說明已下完
524 
525                     
526 
527                     //更新進度
528 
529                     self.progressBlock();
530 
531                 }else{
532 
533                     
534 
535                     //416 Requested Range Not Satisfiable
536 
537                     self.error = [[NSErroralloc]initWithDomain:[self.urlabsoluteString]code:416userInfo:response.allHeaderFields];
538 
539                 }
540 
541             }
542 
543         }
544 
545         return;
546 
547     }else{
548 
549         
550 
551         //其他情況還沒發現
552 
553         return;
554 
555     }
556 
557     
558 
559     //向文件追加數據
560 
561     NSFileHandle *fileHandle = [NSFileHandlefileHandleForUpdatingAtPath:self.targetPath];
562 
563     [fileHandle seekToEndOfFile]; //將節點跳到文件的末尾
564 
565     
566 
567     [fileHandle writeData:data];//追加寫入數據
568 
569     [fileHandle closeFile];
570 
571     
572 
573     //更新進度
574 
575     self.totalReceivedContentLength += data.length;
576 
577     self.progressBlock();
578 
579 }
580 
581  
582 
583  
584 
585 @end

 

 

 

 

 

經驗證,如果app後臺能運行,datatask是支持後臺傳輸的。
讓您的app成為後臺運行app非常簡單:


#import "AppDelegate.h"
static UIBackgroundTaskIdentifier bgTask;


@interface AppDelegate ()


@end


@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    return YES;
}


- (void)applicationDidEnterBackground:(UIApplication *)application {
    
    [self getBackgroundTask];
}


- (void)applicationWillEnterForeground:(UIApplication *)application {
    
    [self endBackgroundTask];
}


/**
 *  獲取後臺任務
 */
-(void)getBackgroundTask{
    
    NSLog(@"getBackgroundTask");
    UIBackgroundTaskIdentifier tempTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        
    }];
    
    if (bgTask != UIBackgroundTaskInvalid) {
        
        [self endBackgroundTask];
    }
    
    bgTask = tempTask;
    
    [self performSelector:@selector(getBackgroundTask) withObject:nil afterDelay:120];
}


/**
 *  結束後臺任務
 */
-(void)endBackgroundTask{
    
    [[UIApplication sharedApplication] endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}


@end


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 模板方法模式 假如我們有一些對象,各個對象之間有一些相同的行為,也有一些不同的行為,這時,我們就可以用模板方法模式來把相同的部分上移到它們的共同原型中(父類),而將不同的部分留給自己各自重新實現。 模板方法:在這些平行對象的共同原型中定義的一個方法,它封裝了子類的演算法框架,它作為一個演算法的模板,指導 ...
  • 原生js使用forEach()與jquery使用each()遍曆數組,return false 的區別: 1、使用each()遍曆數組a,如下: 結果如下: 從運行的效果可以看出,return 相當於迴圈中的break,直接結束整個迴圈。 2、使用forEach()遍曆數組a,如下: 結果如下: 從 ...
  • 《JavaScript權威指南》中指出:JavaScript變數在聲明之前已經可用,JavaScript的這個特性被非正式的稱為聲明提前(hoisting),即JavaScript函數中聲明的所有變數(但不涉及賦值)都被“提前”至函數的頂部。下麵我們從實例中看看: 實例1: 調用函數myFunc() ...
  • form 轉化為真正的數組 先說一下使用場景,在Js中,我們要經常操作DOM,比如獲取全部頁面的input標簽,並且找到類型為button的元素,然後給這個按鈕註冊一個點擊事件,我們可能會這樣操作; 這樣寫肯定是沒有問題的,但是我們知道很多操作數組的方法比for迴圈好用多了,比如es5的forEac ...
  • 概述 現在的開發工具基本都用AndroidStudio了。網上的開源框架也是。比如做瀑布式UI的StaggeredGridView,還有導航頁的PagerSlidingTabStrip等。 那麼電腦性能不好的,還在用eclipse怎麼使用這些開源框架呢? 步驟 準備工作 下載對應的框架如Stagge ...
  • UITextField游標消失 修改以下設置 改為 即可 ...
  • 1、重覆添加某個文件。解決辦法:搜索工程,刪除多餘的文件; 2、文件添加引用錯誤,即尾碼 .m 誤寫為 .h 。解決辦法:改正,編譯通過。 ...
  • Handoff簡介 Handoff是iOS 8 和 OS X v10.10中引入的功能,可以讓同一個用戶在多台設備間傳遞項目。In iOS 9 and OS X v10.11 支持了Spotlight中搜索並打開應用。 Handoff交互: 在iOS中這個user activity object是U ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...