采用NSURLSession实现断点续传-下载

2018-09-07  本文已影响78人  小米咸鱼

最近闲暇时间学习了一下断点续传,下面的是采用系统提供的NSURLSession实现的。


NSURLSession实现方式

#import <Foundation/Foundation.h>

typedef void (^RSBKPDownloadSizeBlock)(NSURLSessionTask* task,NSProgress* progress);
typedef void (^RSBKPDownloadComplateBlock)(NSURLSessionTask* task,NSError* error);


/**
 网络请求 断点续传
 
 */
@interface RSNetWorkRequestBKPDownload : NSObject


/**
 断点续传-下载文件 (自动开启下载,当文件被下载完成后,再起下载该文件,且保存路径不变时,会调用文件下载完成,error为canceled)
 
 @param url 文件全路径地址 - http://......
 @param path 保存文件路径(断点下载与保存路径相关)
 @return 下载任务
 */
+ (NSURLSessionDataTask *)RS_DownloadURL:(NSString *)url toPath:(NSString *)path receiveResponseBlock:(RSBKPDownloadSizeBlock)receiveResponseBlock receiveDataBlock:(RSBKPDownloadSizeBlock)receiveDataBlock completeBlock:(RSBKPDownloadComplateBlock)downComplateBlock;

/*---------------------------重启--------------------------------*/
+ (void)RS_ResumeTask:(NSURLSessionTask*)task;
+ (void)RS_ResumeTaskForURL:(NSString*)url;
+ (void)RS_ResumeAllTasks;

/*-----------------------------暂停------------------------------*/
+ (void)RS_SuspendTask:(NSURLSessionTask*)task;
+ (void)RS_SuspendTaskForURL:(NSString*)url;
+ (void)RS_SuspendAllTasks;

/*----------------------------取消-------------------------------*/
+ (void)RS_CancelTask:(NSURLSessionTask*)task;
+ (void)RS_CancelTaskForURL:(NSString*)url;
+ (void)RS_CancelAllTask;


/**
 销毁session,防止session对d其代理的强引用,而造成内存泄漏
 */
+ (void)RS_invalidate;
@end

#import "RSNetWorkRequestBKPDownload.h"
#import "NSString+String.h"

/**
 下载信息模型
 */
@interface RSBKPModel:NSObject

@property(nonatomic,copy) NSString*                     url;
@property(nonatomic,copy) NSString*                     savePath;
@property(nonatomic,copy) RSBKPDownloadSizeBlock        receiveResponseBlock;
@property(nonatomic,copy) RSBKPDownloadSizeBlock        receiveDataBlock;
@property(nonatomic,copy) RSBKPDownloadComplateBlock    downComplateBlock;
@property(nonatomic,strong) NSFileHandle*               fileHandle;
@property(nonatomic,strong) NSProgress*                 progress;
@end

@implementation RSBKPModel

@end

@interface RSNetWorkRequestBKPDownload()<NSURLSessionDataDelegate>

@property(nonatomic,strong) NSMutableDictionary*        downloadInfoData;
@property(nonatomic,strong) NSMutableDictionary*        downLoadingTasks;
@property(nonatomic,strong) NSURLSession*               session;
@end

//static NSURLSession*            session;
@implementation RSNetWorkRequestBKPDownload

+ (instancetype)share{
    static RSNetWorkRequestBKPDownload* BKPDownload;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        BKPDownload = [RSNetWorkRequestBKPDownload new];
    });
    return BKPDownload;
}

+ (NSURLSessionDataTask*)RS_DownloadURL:(NSString *)url toPath:(NSString *)path receiveResponseBlock:(RSBKPDownloadSizeBlock)receiveResponseBlock receiveDataBlock:(RSBKPDownloadSizeBlock)receiveDataBlock completeBlock:(RSBKPDownloadComplateBlock)downComplateBlock{
    return [[self share] _RS_downloadURL:url toPath:path receiveResponseBlock:receiveResponseBlock receiveDataBlock:receiveDataBlock completeBlock:downComplateBlock];
}

+ (void)RS_ResumeTask:(NSURLSessionDataTask*)task{
    if (task && task.state == NSURLSessionTaskStateSuspended){
        [task resume];
    }
}

+ (void)RS_ResumeTaskForURL:(NSString*)url{
    NSURLSessionTask* task = [[self share] downLoadingTasks][url.MD5];
    [self RS_ResumeTask:task];
}

+ (void)RS_ResumeAllTasks{
    [[[self share] downLoadingTasks].allValues enumerateObjectsUsingBlock:^(NSURLSessionTask*  _Nonnull task, NSUInteger idx, BOOL * _Nonnull stop) {
        [self RS_ResumeTask:task];
    }];
}

+ (void)RS_SuspendTask:(NSURLSessionDataTask*)task{
    if (task && task.state == NSURLSessionTaskStateRunning){
        [task suspend];
    }
}

+ (void)RS_SuspendTaskForURL:(NSString*)url{
    NSURLSessionTask* task = [[self share] downLoadingTasks][url.MD5];
    [self RS_SuspendTask:task];
}

+ (void)RS_SuspendAllTasks{
    [[[self share] downLoadingTasks].allValues enumerateObjectsUsingBlock:^(NSURLSessionTask*  _Nonnull task, NSUInteger idx, BOOL * _Nonnull stop) {
        [self RS_SuspendTask:task];
    }];
}

+ (void)RS_CancelTask:(NSURLSessionTask*)task{
    if (task && (task.state == NSURLSessionTaskStateRunning || task.state == NSURLSessionTaskStateSuspended)){
        [task cancel];
    }
}

+ (void)RS_CancelTaskForURL:(NSString*)url{
    NSURLSessionTask* task = [[self share] downLoadingTasks][url.MD5];
    [self RS_CancelTask:task];
}

+ (void)RS_CancelAllTask{
    [[[self share] downLoadingTasks].allValues enumerateObjectsUsingBlock:^(NSURLSessionTask*  _Nonnull task, NSUInteger idx, BOOL * _Nonnull stop) {
        [self RS_CancelTask:task];
    }];
}
+ (void)RS_invalidate{
    [[[RSNetWorkRequestBKPDownload share] session] invalidateAndCancel];
    [RSNetWorkRequestBKPDownload share].session = nil;
}


#pragma mark - private
- (NSURLSessionDataTask *)_RS_downloadURL:(NSString *)url toPath:(NSString *)path receiveResponseBlock:(RSBKPDownloadSizeBlock)receiveResponseBlock receiveDataBlock:(RSBKPDownloadSizeBlock)receiveDataBlock completeBlock:(RSBKPDownloadComplateBlock)downComplateBlock{
    NSURLSessionDataTask* urlTask = self.downLoadingTasks[url.MD5];
    if (urlTask) {
        return urlTask;
    }
    NSFileManager* fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:path]) {
        [fileManager createFileAtPath:path contents:nil attributes:nil];
    }

    NSError* error;
    NSDictionary* fileInfo = [fileManager attributesOfItemAtPath:path error:&error];
    if (error) {
        if (downComplateBlock) {
            downComplateBlock(nil,error);
        }
        return nil;
    }
    
    NSFileHandle* fileHandle = [NSFileHandle fileHandleForWritingAtPath:path];
    unsigned long long location = fileInfo.fileSize;
    
    NSURLRequest* reuqest = [self _RS_initDownloadRequest:url withBytesLength:location];
    NSURLSessionDataTask* task = [self.session dataTaskWithRequest:reuqest];
    //创建model保存相关信息
    NSProgress* progress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
    progress.completedUnitCount = location;
    RSBKPModel* model = [RSBKPModel new];
    model.url = url;
    model.savePath = path;
    model.receiveResponseBlock = receiveResponseBlock;
    model.receiveDataBlock = receiveDataBlock;
    model.downComplateBlock = downComplateBlock;
    model.fileHandle = fileHandle;
    model.progress = progress;
    //缓存model
    [self.downloadInfoData setObject:model forKey:@(task.taskIdentifier)];
    //将任务标记为正在运行
    [self.downLoadingTasks setObject:task forKey:url.MD5];
    [task resume];
    return task;
}

- (NSURLRequest*)_RS_initDownloadRequest:(NSString*)url withBytesLength:(unsigned long long)length{
    NSURL* reqUrl = [NSURL URLWithString:url];
    NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:reqUrl];
    // 设置HTTP请求头中的Range
    NSString* range = [NSString stringWithFormat:@"bytes=%llu-", length];
    [request setValue:range forHTTPHeaderField:@"Range"];
    return request;
}

#pragma mark - NSULRSessionDataDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler{
    RSBKPModel* currentModel = self.downloadInfoData[@(dataTask.taskIdentifier)];
    if (response.expectedContentLength > currentModel.progress.completedUnitCount) {
        currentModel.progress.totalUnitCount = response.expectedContentLength + currentModel.progress.completedUnitCount;
        if (currentModel.receiveResponseBlock) {
            currentModel.receiveResponseBlock(dataTask, currentModel.progress);
        }
        completionHandler(NSURLSessionResponseAllow);
    }else{
        completionHandler(NSURLSessionResponseCancel);
    }
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
    RSBKPModel* currentModel = self.downloadInfoData[@(dataTask.taskIdentifier)];
    // 指定数据的写入位置 -- 文件内容的最后面
    [currentModel.fileHandle seekToEndOfFile];
    // 向沙盒写入数据
    [currentModel.fileHandle writeData:data];
    currentModel.progress.completedUnitCount += data.length;
    if (currentModel.receiveDataBlock) {
        currentModel.receiveDataBlock(dataTask, currentModel.progress);
    }
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    RSBKPModel* currentModel = self.downloadInfoData[@(task.taskIdentifier)];
    [currentModel.fileHandle closeFile];
    [self.downloadInfoData removeObjectForKey:task];
    [self.downLoadingTasks removeObjectForKey:currentModel.url.MD5];
    if (currentModel.downComplateBlock) {
        currentModel.downComplateBlock(task,error);
    }
}

#pragma mark - getter
- (NSURLSession *)session{
    if (!_session) {
        _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
    }
    return _session;
}

- (NSMutableDictionary *)downloadInfoData{
    if (!_downloadInfoData) {
        _downloadInfoData = @{}.mutableCopy;
    }
    return _downloadInfoData;
}

/**
 保存正在进行的下载任务

 @return 字典
 */
- (NSMutableDictionary*)downLoadingTasks{
    if (!_downLoadingTasks) {
        _downLoadingTasks = @{}.mutableCopy;
    }
    return _downLoadingTasks;
}
@end

上一篇下一篇

猜你喜欢

热点阅读