ios 後臺下載,app退出再進入可以斷點續傳 NSURLSessionDownloadTask(一)
阿新 • • 發佈:2019-01-27
想了解,多檔案下載和管理,看這一篇文章,是在這基礎上再次封裝的:點選開啟連結
使用NSURLSessionDataTask,進行封裝下載的,看這篇文章 點選開啟連結
使用:
#import "ViewController.h" #import "BackgroundDownloadTool.h" @interface ViewController ()<BackgroundDownloadToolDelegate> { BackgroundDownloadTool *tool; } @property (weak, nonatomic) IBOutlet UILabel *label; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. tool = [BackgroundDownloadTool new]; tool.delegate = self; [tool initTask]; } -(void)BackgroundDownloadToolCallbackProgress:(double)progress Error:(NSError *)error { NSLog(@"%f",progress); self.label.text = [NSString stringWithFormat:@"%f",progress]; } - (IBAction)start:(id)sender { [tool startOrContinueDownload]; } - (IBAction)stop:(id)sender { [tool pauseDownload]; }
主要程式碼:
AppDelegate.m
BackgroundDownloadTool.h#import "AppDelegate.h" #import "BackgroundDownloadTool.h" @interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. return YES; } -(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler { [BackgroundDownloadTool sharedInstance].backgroundSessionCompletionHandler = completionHandler; }
#import <Foundation/Foundation.h> #import <UIKit/UIKit.h> @protocol BackgroundDownloadToolDelegate<NSObject> @optional -(void)BackgroundDownloadToolCallbackProgress:(double)progress Error:(NSError *)error identifier:(NSString *)identifier; -(void)BackgroundDownloadToolDownloadingFinish:(NSString *)path identifier:(NSString *)identifier; @end @interface BackgroundDownloadTool : NSObject @property(nonatomic,strong)NSString *identifier;//唯一SessionConfiguration @property(nonatomic,strong)NSString *fileName;//儲存的檔名 @property(nonatomic,strong)NSString *urlStr; @property (nonatomic, weak) IBOutlet id<BackgroundDownloadToolDelegate> delegate; @property (strong, nonatomic)NSData *resumeData;//已下載的資料 @property (nonatomic, copy) void (^backgroundSessionCompletionHandler)(); - (void)startOrContinueDownload;//開始或繼續下載 - (void)pauseDownload;//暫停下載 -(void)cancelDownload;//取消下載 -(void)removeDownloadFile;//移除已下載好的檔案 -(void)removeCacheDownloadFile;//移除快取的檔案 - (void)downloadProgress:(void (^)(CGFloat progress, NSError *err))downloadProgressBlock complement:(void (^)(NSString *path))completeBlock; @end
BackgroundDownloadTool.m
#import "BackgroundDownloadTool.h"
#import "NSURLSession+TYCorrectedResumeData.h"
#define IS_IOS10ORLATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10)
#define IS_IOS8ORLATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8)
@interface BackgroundDownloadTool ()<NSURLSessionDelegate, NSURLSessionTaskDelegate,NSURLSessionDownloadDelegate> {
NSString *tmpPath;
BOOL _isDownloadStateCompleted;
int64_t allSize;
BOOL _isStart;
}
@property (nonatomic) NSURLSession *session;
@property (nonatomic) NSURLSessionDownloadTask *downloadTask;
@property (nonatomic,strong) NSString *DownloadPath;
@property (nonatomic,strong) NSString *CachePath;
@property (nonatomic,strong) NSString *Location;
@property (nonatomic,strong) NSFileManager *manage;
@property (strong, nonatomic) NSOperationQueue *queue;
@property(nonatomic,copy)void (^downloadProgressBlock)(CGFloat progress, NSError *err);
@property(nonatomic,copy)void (^completeBlock)(NSString *path);
@end
@implementation BackgroundDownloadTool
-(NSString *)Location {
if (!_Location) {
_Location =[[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"BackgroundDownload"];
[self.manage createDirectoryAtPath:_Location withIntermediateDirectories:YES attributes:nil error:nil];
}
return _Location;
}
- (NSString *)DownloadPath {
if (!_DownloadPath) {
_DownloadPath =[self.Location stringByAppendingPathComponent:self.fileName];
}
return _DownloadPath;
}
- (NSString *)CachePath {
if (!_CachePath) {
_CachePath =[self.Location stringByAppendingPathComponent:[NSString stringWithFormat:@"cashe%@",self.fileName]];
}
return _CachePath;
}
//初始化session
-(NSURLSession *)session {
if (!_session) {
if (_identifier) {
if (IS_IOS8ORLATER) {
NSURLSessionConfiguration *configure = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:_identifier];
_session = [NSURLSession sessionWithConfiguration:configure delegate:self delegateQueue:self.queue];
}else{
_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration backgroundSessionConfiguration:_identifier] delegate:self delegateQueue:self.queue];
}
}else {
_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:self.queue];
}
}
return _session;
}
- (NSOperationQueue *)queue {
if (!_queue) {
_queue = [[NSOperationQueue alloc]init];
_queue.maxConcurrentOperationCount = 1;
}
return _queue;
}
- (NSFileManager *)manage{
if (!_manage){
_manage = [NSFileManager defaultManager];
}
return _manage;
}
-(instancetype)init{
if (self = [super init]) {
self.identifier = @"BackgroundSession.tmp";
self.urlStr = @"http://baobab.wdjcdn.com/1456317490140jiyiyuetai_x264.mp4";
self.fileName = @"test.mp4";
}
return self;
}
+(BackgroundDownloadTool *)sharedInstance {
static dispatch_once_t pred = 0;
__strong static id internet = nil;
dispatch_once(&pred, ^{
internet = [[self alloc] init];
});
return internet;
}
- (void)downloadProgress:(void (^)(CGFloat progress, NSError *err))downloadProgressBlock complement:(void (^)(NSString *path))completeBlock {
self.downloadProgressBlock = downloadProgressBlock;
self.completeBlock = completeBlock;
}
-(void)initTask {
NSURL *downloadURL = [NSURL URLWithString:self.urlStr];
NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL];
self.downloadTask = [self.session downloadTaskWithRequest:request];
}
-(NSInteger)AlreadyDownloadLength {
return [[[NSFileManager defaultManager]attributesOfItemAtPath:self.DownloadPath error:nil][NSFileSize] integerValue];
}
// 是否已經下載
- (BOOL)isDownloadCompletedWithDownload {
return [self.manage fileExistsAtPath:self.DownloadPath];
}
-(void)startOrContinueDownload {
if (_isStart) {
return;
}
if (![self isDownloadCompletedWithDownload]) {
[self initTask];
self.resumeData = [NSData dataWithContentsOfFile:self.CachePath];
if (self.resumeData) {
if (IS_IOS10ORLATER) {
self.downloadTask = [self.session downloadTaskWithCorrectResumeData:self.resumeData];
} else {
self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
}
}
_isStart = YES;
[self.downloadTask resume];
}else {
NSLog(@"已經下載完成");
!self.completeBlock?:self.completeBlock(self.DownloadPath);
if ([self.delegate respondsToSelector:@selector(BackgroundDownloadToolDownloadingFinish:identifier:)]) {
[self.delegate BackgroundDownloadToolDownloadingFinish:self.DownloadPath identifier:self.identifier];
}
}
}
-(void)cancelDownload {
_isStart = NO;
[self.downloadTask suspend];
[self.downloadTask cancel];
self.downloadTask = nil;
[self removeCacheDownloadFile];
}
- (void)pauseDownload {
_isStart = NO;
if (![self isDownloadCompletedWithDownload]) {
__weak __typeof(self) wSelf = self;
[self.downloadTask cancelByProducingResumeData:^(NSData * resumeData) {
__strong __typeof(wSelf) sSelf = wSelf;
sSelf.resumeData = resumeData;
[sSelf saveData:resumeData];
}];
}else {
NSLog(@"已經下載完成");
}
}
-(void)saveData:(NSData *)data {
[data writeToFile:self.CachePath atomically:YES];
}
//監聽進度
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
if (downloadTask == self.downloadTask) {
allSize = totalBytesExpectedToWrite;
double progress = (double)totalBytesWritten / (double)totalBytesExpectedToWrite;
dispatch_async(dispatch_get_main_queue(), ^{
!self.downloadProgressBlock?:self.downloadProgressBlock(progress,nil);
if ([self.delegate respondsToSelector:@selector(BackgroundDownloadToolCallbackProgress:Error:identifier:)]) {
[self.delegate BackgroundDownloadToolCallbackProgress:progress Error:nil identifier:self.identifier];
}
});
}
}
-(void)removeDownloadFile {
NSError *error;
if ([self.manage fileExistsAtPath:self.DownloadPath ] ) {
[self.manage removeItemAtPath:self.DownloadPath error:&error];
if (error) {
NSLog(@"removeItem error %@",error);
}
}
}
-(void)removeCacheDownloadFile {
NSError *error;
if ([self.manage fileExistsAtPath:self.CachePath ] ) {
[self.manage removeItemAtPath:self.CachePath error:&error];
if (error) {
NSLog(@"removeItem error %@",error);
}
}
}
//下載成功
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
NSLog(@"downloadTask:%lu didFinishDownloadingToURL:%@", (unsigned long)downloadTask.taskIdentifier, location);
NSString *locationString = [location path];
NSError *error;
[self removeDownloadFile];
[self removeCacheDownloadFile];
[self.manage moveItemAtPath:locationString toPath:self.DownloadPath error:&error];
if (error) {
NSLog(@"moveItemAtPath error %@",error);
}
_isStart = NO;
!self.completeBlock?:self.completeBlock(self.DownloadPath);
if ([self.delegate respondsToSelector:@selector(BackgroundDownloadToolDownloadingFinish:identifier:)]) {
[self.delegate BackgroundDownloadToolDownloadingFinish:self.DownloadPath identifier:self.identifier];
}
}
//下載完成
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (error) {
_isStart = NO;
if ([error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData]) {
NSData *data = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData];
self.resumeData = data;
[self saveData:data];
}
}
double progress = (double)task.countOfBytesReceived / (double)task.countOfBytesExpectedToReceive;
dispatch_async(dispatch_get_main_queue(), ^{
!self.downloadProgressBlock?:self.downloadProgressBlock(progress,error);
if ([self.delegate respondsToSelector:@selector(BackgroundDownloadToolCallbackProgress:Error:identifier:)]) {
[self.delegate BackgroundDownloadToolCallbackProgress:progress Error:error identifier:self.identifier];
}
});
self.downloadTask = nil;
}
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
if (self.backgroundSessionCompletionHandler) {
self.backgroundSessionCompletionHandler();
}
}
@end
NSURLSession+TYCorrectedResumeData.h
#import "NSURLSession+TYCorrectedResumeData.h"
#import <UIKit/UIKit.h>
#define IS_IOS10ORLATER ([[[UIDevice currentDevice] systemVersion] floatValue] >= 10)
@implementation NSURLSession (TYCorrectedResumeData)
- (NSURLSessionDownloadTask *)downloadTaskWithCorrectResumeData:(NSData *)resumeData {
NSString *kResumeCurrentRequest = @"NSURLSessionResumeCurrentRequest";
NSString *kResumeOriginalRequest = @"NSURLSessionResumeOriginalRequest";
NSData *cData = correctResumeData(resumeData);
cData = cData?cData:resumeData;
NSURLSessionDownloadTask *task = [self downloadTaskWithResumeData:cData];
NSMutableDictionary *resumeDic = getResumeDictionary(cData);
if (resumeDic) {
if (task.originalRequest == nil) {
NSData *originalReqData = resumeDic[kResumeOriginalRequest];
NSURLRequest *originalRequest = [NSKeyedUnarchiver unarchiveObjectWithData:originalReqData ];
if (originalRequest) {
[task setValue:originalRequest forKey:@"originalRequest"];
}
}
if (task.currentRequest == nil) {
NSData *currentReqData = resumeDic[kResumeCurrentRequest];
NSURLRequest *currentRequest = [NSKeyedUnarchiver unarchiveObjectWithData:currentReqData];
if (currentRequest) {
[task setValue:currentRequest forKey:@"currentRequest"];
}
}
}
return task;
}
#pragma mark- private
NSData * correctRequestData(NSData *data) {
if (!data) {
return nil;
}
// return the same data if it's correct
if ([NSKeyedUnarchiver unarchiveObjectWithData:data] != nil) {
return data;
}
NSMutableDictionary *archive = [[NSPropertyListSerialization propertyListWithData:data options:NSPropertyListMutableContainersAndLeaves format:nil error:nil] mutableCopy];
if (!archive) {
return nil;
}
NSInteger k = 0;
id objectss = archive[@"$objects"];
while ([objectss[1] objectForKey:[NSString stringWithFormat:@"$%ld",k]] != nil) {
k += 1;
}
NSInteger i = 0;
while ([archive[@"$objects"][1] objectForKey:[NSString stringWithFormat:@"__nsurlrequest_proto_prop_obj_%ld",i]] != nil) {
NSMutableArray *arr = archive[@"$objects"];
NSMutableDictionary *dic = arr[1];
id obj = [dic objectForKey:[NSString stringWithFormat:@"__nsurlrequest_proto_prop_obj_%ld",i]];
if (obj) {
[dic setValue:obj forKey:[NSString stringWithFormat:@"$%ld",i+k]];
[dic removeObjectForKey:[NSString stringWithFormat:@"__nsurlrequest_proto_prop_obj_%ld",i]];
[arr replaceObjectAtIndex:1 withObject:dic];
archive[@"$objects"] = arr;
}
i++;
}
if ([archive[@"$objects"][1] objectForKey:@"__nsurlrequest_proto_props"] != nil) {
NSMutableArray *arr = archive[@"$objects"];
NSMutableDictionary *dic = arr[1];
id obj = [dic objectForKey:@"__nsurlrequest_proto_props"];
if (obj) {
[dic setValue:obj forKey:[NSString stringWithFormat:@"$%ld",i+k]];
[dic removeObjectForKey:@"__nsurlrequest_proto_props"];
[arr replaceObjectAtIndex:1 withObject:dic];
archive[@"$objects"] = arr;
}
}
// Rectify weird "NSKeyedArchiveRootObjectKey" top key to NSKeyedArchiveRootObjectKey = "root"
if ([archive[@"$top"] objectForKey:@"NSKeyedArchiveRootObjectKey"] != nil) {
[archive[@"$top"] setObject:archive[@"$top"][@"NSKeyedArchiveRootObjectKey"] forKey: NSKeyedArchiveRootObjectKey];
[archive[@"$top"] removeObjectForKey:@"NSKeyedArchiveRootObjectKey"];
}
// Reencode archived object
NSData *result = [NSPropertyListSerialization dataWithPropertyList:archive format:NSPropertyListBinaryFormat_v1_0 options:0 error:nil];
return result;
}
NSMutableDictionary *getResumeDictionary(NSData *data) {
NSMutableDictionary *iresumeDictionary = nil;
if (IS_IOS10ORLATER) {
id root = nil;
id keyedUnarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
@try {
root = [keyedUnarchiver decodeTopLevelObjectForKey:@"NSKeyedArchiveRootObjectKey" error:nil];
if (root == nil) {
root = [keyedUnarchiver decodeTopLevelObjectForKey:NSKeyedArchiveRootObjectKey error:nil];
}
} @catch(NSException *exception) {
}
[keyedUnarchiver finishDecoding];
iresumeDictionary = [root mutableCopy];
}
if (iresumeDictionary == nil) {
iresumeDictionary = [NSPropertyListSerialization propertyListWithData:data options:NSPropertyListMutableContainersAndLeaves format:nil error:nil];
}
return iresumeDictionary;
}
NSData *correctResumeData(NSData *data) {
NSString *kResumeCurrentRequest = @"NSURLSessionResumeCurrentRequest";
NSString *kResumeOriginalRequest = @"NSURLSessionResumeOriginalRequest";
if (data == nil) {
return nil;
}
NSMutableDictionary *resumeDictionary = getResumeDictionary(data);
if (resumeDictionary == nil) {
return nil;
}
resumeDictionary[kResumeCurrentRequest] = correctRequestData(resumeDictionary[kResumeCurrentRequest]);
resumeDictionary[kResumeOriginalRequest] = correctRequestData(resumeDictionary[kResumeOriginalRequest]);
NSData *result = [NSPropertyListSerialization dataWithPropertyList:resumeDictionary format:NSPropertyListXMLFormat_v1_0 options:0 error:nil];
return result;
}
@end
我的業餘技術微信公眾號:YKJGZH,歡迎大家進入