断点续传功能的实现

来源:互联网 发布:五常大米价格 知乎 编辑:程序博客网 时间:2024/06/06 03:19

断点续传功能的实现

  • 断点续传功能的实现
    • 下载状态和下载处理Block的定义
    • 定义下载任务
      • CMDownloadTask类的定义
      • changeCurrentStatus类方法实现
      • 开始下载
      • 暂停下载
      • 取消下载
      • 通知观察者
      • NSURLSessionDownloadDelegate协议方法
    • 任务列表
    • 结语

项目中需要使用到断点续传的功能,用于群文件共享的下载,本来是打算使用公司原来用C++写的下载,但是实在太麻烦、不方便,几经思考,决定自己动手使用NSURL系列方法来实现,现将代码纪录以供将来重复使用。

下载状态和下载处理Block的定义

首先需要定义下载的状态以及接收到数据时留出的回调Block,包括一些宏定义,如下所示:

typedef NS_ENUM(NSUInteger, DownloadStatus) {    DownloadStatusNone,                     //无状态    DownloadStatusDowloading,               //正在下载    DownloadStatusPaused,                   //暂停下载    DownloadStatusCancelled,                //已取消下载    DownloadStatusDownloaded                //完成下载};typedef void(^DownloadProgressHandler)(CGFloat, CGFloat);#define DOWNLOAD_FILE_PATH(fileName) [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:[NSString stringWithFormat:@"ulp/download/%@",(fileName)]]#define DOWNLOAD_FILE_PATH_WITH_EXTENSION(fileName,fileExtension    [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:[NSString stringWithFormat:@"ulp/download/%@.%@",(fileName),(fileExtension)]]#define DownloadFailedNotification @"DownloadFailedNotification"#define DownloadCompleteNotification @"DownloadCompleteNotification"

DownloadProgressHandler主要是留给UI用于更新UIProgressView及其他指示性的文本。

定义下载任务

CMDownloadTask类的定义

类CMDownloadTask表示一个可能处在任何状态的一个下载任务,代码如下所示:

@interface CMDownloadTask : NSObject <NSURLSessionDownloadDelegate> {    DownloadStatus theCurrentStatus;    NSMutableDictionary* observerDict;}@property(nonatomic,strong) NSURLSessionDownloadTask* downloadTask;@property(nonatomic,strong) NSData* resumeData;@property(nonatomic,copy) NSString* downloadUrl;@property(nonatomic,copy) NSString* downloadFileName;@property(nonatomic,strong) NSDate* previousDownloadDate;-(instancetype)initWithUrl:(NSString*)urlStr downloadFileName:(NSString*)fileName;-(DownloadStatus)getCurrentStatus;-(void)changeCurrentStatus:(DownloadStatus)status;-(void)addObserverWithBlock:(DownloadProgressHandler)downloadHandler WithUrl:(NSString*)urlStr;-(void)removeObserverWithUrl:(NSString*)urlStr;-(void)removeAllObservers;@end

其中的observerDict是用于实现观察者模式,用户可以通过addObserver/removeObserver/removeAllObservers来增加和删除观察者。

changeCurrentStatus类方法实现

changeCurrentStatus则是使用了状态模式,通过状态的切换来执行不同的操作,代码如下所示:

-(void)changeCurrentStatus:(DownloadStatus)status {    if(status == theCurrentStatus)        return ;    switch (status) {        case DownloadStatusDowloading:            [self startDownload];            break;        case DownloadStatusDownloaded:            [[CMDownloadTaskList getSharedInstance] removeDownloadTaskByUrl:self.downloadUrl];            [[NSNotificationCenter defaultCenter] postNotificationName:DownloadCompleteNotification object:nil];            break;        case DownloadStatusCancelled:            [self cancelDownload];            break;        case DownloadStatusPaused:            [self pauseDownload];            break;        default:            break;    }    theCurrentStatus = status;    return ;}

开始下载

-(void)startDownload {    NSURLSession* downloadSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];    __weak typeof(self) weakSelf = self;    if(self.resumeData == nil)        self.downloadTask = [downloadSession downloadTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.downloadUrl]]];    else        self.downloadTask = [downloadSession downloadTaskWithResumeData:self.resumeData];    self.previousDownloadDate = [NSDate date];    [self.downloadTask resume];    [[CMDownloadTaskList getSharedInstance] addDownloadTask:self];    return ;}

resumeData用于保存已下载的数据,当resumeData为空的时候,表示没有下载过任何数据,初始化一个下载任务并立即开始下载。如果已经保存有下载的数据,则使用已下载的数据再继续下载,即使用self.resumeData初始化下载任务。

暂停下载

-(void)pauseDownload {    __weak typeof(self) weakSelf = self;    [self.downloadTask cancelByProducingResumeData:^(NSData *resumeData) {        weakSelf.resumeData = resumeData;    }];    return ;}

暂停下载的代码非常简单,主要就是将已经下载的部分数据保存到self.resumeData当中,然后取消当前下载。

取消下载

-(void)cancelDownload {    [self.downloadTask cancel];    if([[NSFileManager defaultManager] fileExistsAtPath:DOWNLOAD_FILE_PATH(self.downloadFileName)])        [[NSFileManager defaultManager] removeItemAtPath:DOWNLOAD_FILE_PATH(self.downloadFileName) error:nil];    self.downloadTask = nil;    return ;}

取消下载之后,如果原来已经下载的部分以文件形式存在,则删除该文件,同时取消下载。

通知观察者

当下载的数据有更新之后,需要通知所有已注册的观察者,故采用方法notifyAllToUpdateWithBytesWritten:TotalBytesToWrite:方法使观察者更新UI,代码如下所示:

-(void)notifyAllToUpdateWithBytesWritten:(CGFloat)bytesWritten TotalBytesToWrite:(CGFloat)totalBytes {    NSEnumerator* enumerator = [observerDict keyEnumerator];    id key = nil;    while(key = [enumerator nextObject]) {        ((DownloadProgressHandler) [observerDict objectForKey:key])(bytesWritten, totalBytes);    }    return ;}

NSURLSessionDownloadDelegate协议方法

-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {    if(error) {        [self cancelDownload];        [[NSNotificationCenter defaultCenter] postNotificationName:DownloadFailedNotification object:nil];    }    return ;}-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {    NSDate* currentDate = [NSDate date];    [self notifyAllToUpdateWithBytesWritten:totalBytesWritten TotalBytesToWrite:totalBytesExpectedToWrite];    return ;}-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {    NSError* error = nil;    [[NSFileManager defaultManager] moveItemAtPath:location.path toPath:DOWNLOAD_FILE_PATH(self.downloadFileName) error:&error];    if(error)        NSLog(@"MoveItemToPath:%@-------------Error:%@",DOWNLOAD_FILE_PATH(self.downloadFileName),error.localizedFailureReason);    else        NSLog(@"MoveItemToPath:--------------%@",DOWNLOAD_FILE_PATH(self.downloadFileName));    [self changeCurrentStatus:DownloadStatusDownloaded];    return ;}

当有下载数据更新的时候,会进入到上面的代码中,当下载失败的时候,发出通知。写入数据的时候,则通知观察者更新UI,当下载完成之后,将下载完的数据移动到指定的目录当中,并修改相应的状态。

任务列表

为支持多任务下载,开辟一个单例链表类,以url做为标识,保存一个CMDownloadTask,代码如图所求:

@interface CMDownloadTaskList : NSObject@property(nonatomic,strong) NSMutableArray* downloadTaskList;+(CMDownloadTaskList*)getSharedInstance;-(CMDownloadTask*)getCurrentDownloadTaskByUrl:(NSString*)urlStr;-(void)addDownloadTask:(CMDownloadTask*)downloadTask;-(void)removeDownloadTaskByUrl:(NSString*)urlStr;@end

用户则可以通过url来获取、增加、删除相应的下载任务。该类的全部代码如下所示:

static CMDownloadTaskList* instance = nil;@implementation CMDownloadTaskList+(CMDownloadTaskList *)getSharedInstance {    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        if(instance == nil) {            instance = [[CMDownloadTaskList alloc] init];            instance.downloadTaskList = [NSMutableArray array];        }    });    return instance;}-(void)addDownloadTask:(CMDownloadTask *)downloadTask {    for (CMDownloadTask* task in self.downloadTaskList) {        if([task.downloadUrl isEqualToString:downloadTask.downloadUrl])            return ;    }    [self.downloadTaskList addObject:downloadTask];    return ;}-(void)removeDownloadTaskByUrl:(NSString *)urlStr {    for (CMDownloadTask* task in self.downloadTaskList) {        if([task.downloadUrl isEqualToString:urlStr]) {            [task removeAllObservers];            [self.downloadTaskList removeObject:task];            return ;        }    }    return ;}-(CMDownloadTask *)getCurrentDownloadTaskByUrl:(NSString *)urlStr {    for (CMDownloadTask* task in self.downloadTaskList) {        if([task.downloadUrl isEqualToString:urlStr])            return task;    }    return nil;}@end

结语

全部代码到这就全部结束了,这几个类并不完善,以后也许会随着项目的要求来完善。

0 0
原创粉丝点击