Music (files) downloaded breakpoint

This article describes the download of music and other files, support for HTTP.

We need to create two classes
HYDownLoader: Music of the main class, you can create a new download, download pause, cancel downloads.
HYFileTool: document management classes, mainly HYDownLoader service, you can determine whether a file exists, move files.

First, the file management tools HYFileTool

HYFileTool class is relatively simple, directly on the code, the method Remarks .h file has been more clearly the
.h file

#import <Foundation/Foundation.h>
@interface HYFileTool : NSObject


/**
 判断文件是否存在

 @param filePath 文件路径
 @return 是否存在
 */
+(BOOL)fileExists:(NSString *)filePath;


/**
 获取文件大小

 @param filePath 文件路径
 @return 文件大小
 */
+(long long)fileSize:(NSString *)filePath;


/**
 移动文件到新的路径

 @param fromPath 文件的原路径
 @param toPath 文件的新路径
 */
+(void)moveFile:(NSString *)fromPath toPath:(NSString *)toPath;


/**
 删除文件

 @param filePath 文件路径
 */
+(void)removeFile:(NSString*)filePath;

@end

.m file

#import "HYFileTool.h"

@implementation HYFileTool

+(BOOL)fileExists:(NSString *)filePath{
    if (filePath.length == 0) {
        
    }
    return [[NSFileManager defaultManager] fileExistsAtPath:filePath];
}

+(long long)fileSize:(NSString *)filePath{
    
    if (![self fileExists:filePath]) {
        return 0;
    }
    
    NSDictionary *fileInfo = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
    return [fileInfo[NSFileSize] longLongValue];
}

+(void)moveFile:(NSString *)fromPath toPath:(NSString *)toPath{
    if (![self fileExists:fromPath]) {
        return;
    }
    [[NSFileManager defaultManager] moveItemAtPath:fromPath toPath:toPath error:nil];
}

+(void)removeFile:(NSString*)filePath{
    [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
}

@end

Second, download HYDownLoader.h main class
defines the enumeration download status in the first HYDownLoader.h

typedef NS_ENUM(NSUInteger,HYDownloadState){
    HYDownloadStatePause,
    HYDownloadStateDowning,
    HYDownloadStateSuccess,
    HYDownloadStateFail
};

And Block in various states callback

typedef void(^DownLoadInfoType)(long long totalSize);
typedef void(^ProgressBlockType)(float progress);
typedef void(^SuccesswBlockType)(NSString *path);
typedef void(^FailBlockType)(void);
typedef void(^StateChangeBlockType)(HYDownloadState state);

Then define the corresponding attribute

/**
 下载状态
 */
@property(nonatomic,assign,readonly) HYDownloadState state;

/**
 下载进度
 */
@property(nonatomic,assign,readonly) float progress;

/**
 下载信息回调
 */
@property(nonatomic,copy) DownLoadInfoType downLoadInfo;

/**
 下载状态改变回调
 */
@property(nonatomic,copy) StateChangeBlockType stateChangeInfo;

/**
 下载进度改变回调
 */
@property(nonatomic,copy) ProgressBlockType progressChange;

/**
 下载成功回调
 */
@property(nonatomic,copy) SuccesswBlockType successBlock;

/**
 下载失败回调
 */
@property(nonatomic,copy) FailBlockType failBlock;

And methods

/**
 下载文件

 @param url 下载文件的网络地址
 */
-(void)downLoader:(NSURL *)url;

/**
 下载文件

 @param url 下载文件的网络地址
 @param downLoadInfo 下载信息block
 @param progressBlock 下载进度block
 @param successBlock 下载成功block
 @param failedBlock 下载失败block
 */
-(void)downLoader:(NSURL *)url downLoadInfo:(DownLoadInfoType)downLoadInfo progress:(ProgressBlockType)progressBlock success:(SuccesswBlockType)successBlock failed:(FailBlockType)failedBlock;


/**
 暂停当前任务
 */
-(void)pauseCurrentTask;


/**
 取消当前任务
 */
-(void)cancelCurrentTask;

/**
 取消并清除当前任务
 */
-(void)cancelAndClean;

Three, HYDownLoader.m the variables

Variables defined internal method HYDownLoader.m required

@interface HYDownLoader()<NSURLSessionDataDelegate>{
    long long _tempSize;//已下载文件大小
    long long _totalSize;//文件总大小
}

@property (nonatomic,strong) NSURLSession *session;
@property (nonatomic,weak) NSURLSessionDataTask *dataTask;

/**
 下载文件完成后的路径
 */
@property (nonatomic,copy) NSString *downloadedPath;
/**
 下载文件时的路径
 */
@property (nonatomic,copy) NSString *downloadingPath;
/**
 写入文件的流
 */
@property (nonatomic,strong) NSOutputStream *outputStream;

@end

Corresponding to the get and set methods

-(NSURLSession*)session{
    if (_session == nil) {
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
        _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    }
    return _session;
}

-(void)setState:(HYDownloadState)state{
    if (_state == state) {
        return;
    }
    _state = state;
    
    if (self.stateChangeInfo) {
        self.stateChangeInfo(_state);
    }
    if (state == HYDownloadStateSuccess && self.successBlock) {
        self.successBlock(_downloadedPath);
    }
    if (state == HYDownloadStateFail && self.failBlock) {
        self.failBlock();
    }
}

-(void)setProgress:(float)progress{
    _progress = progress;
    
    if (self.progressChange) {
        self.progressChange(progress);
    }
}

Where setState, we inform the outside world, depending on the download status callback corresponding Block.

Fourth, the main method for downloading downLoader HYDownLoader.m of: and downloadWithURL: offset:

-(void)downLoader:(NSURL *)url{

    //如果任务存在,当前只是暂停,则继续下载
    if ([url isEqual:self.dataTask.originalRequest.URL] && self.state == HYDownloadStatePause) {
        [self resumeCurrenttask];
        return;
    }
    
    //1.文件的存放
    //下载时存放到temp(此目录用于存放临时文件,app退出时会被清理)
    //下载完成后移动到cache(iTunes不会备份此目录,此目录下文件不会在app退出时删除)
    NSString *fileName = url.lastPathComponent;
    self.downloadedPath = [kCachePath stringByAppendingPathComponent:fileName];
    self.downloadingPath = [kTmpPath stringByAppendingPathComponent:fileName];
    
    //1.判断url地址对应的资源是否已下载完成
    //1.1如果已完成,则返回相关信息
    if([HYFileTool fileExists:self.downloadedPath]){
        NSLog(@"已下载完成(文件已存在)");
        self.state = HYDownloadStateSuccess;
        return;
    }
    
    [self downloadWithURL:url offset:0];

    
    //2.否则检查临时文件是否存在
    //2.1若存在,以当前已存在文件大小,作为开始字节请求资源。
    if ([HYFileTool fileExists:self.downloadingPath]) {
        //获取本地文件大小(已下载部分)
        _tempSize = [HYFileTool fileSize:self.downloadingPath];
        [self downloadWithURL:url offset:_tempSize];
        return;
    }
    
    // 本地大小 == 总大小 则移动到cache文件夹
    // 本地大小 > 总大小  则删除本地缓存,重新从0开始下载
    // 本地大小 < 总大小  从本地大小开始下载
    
    //2.2 不存在,则从0字节开始请求资源
    [self downloadWithURL:url offset:0];
    
}

Write the callback method with a block of time as long as the assignment to each block, and calls the main method to download above:

-(void)downLoader:(NSURL *)url downLoadInfo:(DownLoadInfoType)downLoadInfo progress:(ProgressBlockType)progressBlock success:(SuccesswBlockType)successBlock failed:(FailBlockType)failedBlock{
    self.downLoadInfo = downLoadInfo;
    self.progressChange = progressBlock;
    self.successBlock = successBlock;
    self.failBlock  = failedBlock;
    [self downLoader:url];
}

downLoader: method is only done on the current state of the judgment, download the core is actually downloadWithURL: offset: methods. NSURLSession system using a network request. HTTP1.1 Protocol (RFC2616) began to support access to parts of the file, which provides technical support for parallel downloads, and HTTP. It corresponds to the time the transmission request from the Header implemented by two parameters, the Range is the client, the server when the corresponding response is Content-Range. So the key is that the HTTP request, the request to set the Range request header, the request indicates that only the back of the offset data.

/**
 根据开始字节,请求资源

 @param url 下载url
 @param offset 开始字节
 */
- (void)downloadWithURL:(NSURL *)url offset:(long long)offset{
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:0];
    [request setValue:[NSString stringWithFormat:@"bytes=%lld-",offset] forHTTPHeaderField:@"Range"];
    self.dataTask = [self.session dataTaskWithRequest:request];
    [self resumeCurrenttask];
}

NSURLSession request a callback proxy mode. HYDownLoader follow NSURLSessionDataDelegate agent, and completed the three agents callback them:

#pragma mark - 协议

//接收到响应头
- (void)URLSession:(NSURLSession *)session dataTask:(nonnull NSURLSessionDataTask *)dataTask didReceiveResponse:(nonnull NSHTTPURLResponse *)response completionHandler:(nonnull void (^)(NSURLSessionResponseDisposition))completionHandler{
    NSLog(@"%@",response);
    _totalSize = [response.allHeaderFields[@"Content-Length"] longLongValue];
    NSString *contentRangeStr = response.allHeaderFields[@"Content-Range"];
    if (contentRangeStr.length > 0) {
        _totalSize = [[[contentRangeStr componentsSeparatedByString:@"/"] lastObject] longLongValue];
    }
    self.downLoadInfo(_totalSize);
    
    if (_tempSize == _totalSize) {
        //文件移动到完成文件夹
        NSLog(@"下载完成,移动文件到完成文件夹");
        [HYFileTool moveFile:_downloadingPath toPath:_downloadedPath];
        completionHandler(NSURLSessionResponseCancel);
        self.state = HYDownloadStateSuccess;
        return;
    }
    
    if (_tempSize > _totalSize) {
        //删除临时缓存
        [HYFileTool removeFile:self.downloadingPath];
        //重新下载
        [self downLoader:response.URL];
        //取消请求
        completionHandler(NSURLSessionResponseCancel);
    }
    
    //继续接受数据
    self.state = HYDownloadStateDowning;
    self.outputStream = [NSOutputStream outputStreamToFileAtPath:self.downloadingPath append:YES];
    completionHandler(NSURLSessionResponseAllow);
}

//继续接收数据
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data{
    
    _tempSize += data.length;
    self.progress = 1.0 * _tempSize / _totalSize;
    
    [self.outputStream write:data.bytes maxLength:data.length];
    NSLog(@"接受数据");
}

//请求结束
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error{
    NSLog(@"接收完成");
    if (error == nil) {
        [HYFileTool moveFile:self.downloadingPath toPath:self.downloadedPath];
        self.state = HYDownloadStateSuccess;
    }else{
        if(error.code == -999){
            NSLog(@"取消下载");
            self.state = HYDownloadStatePause;
        }else{
            NSLog(@"下载错误%@",error);
            self.state = HYDownloadStateFail;
        }
    }
    [self.outputStream close];
}

The core point we explained in detail below:

  1. After receiving the header information didReceiveResponse through completionHandler callback, the decision operation of the subsequent content, such as a cancellation request or continue to receive data
  2. Write to the file by way outputStream stream.
  3. After receiving the documents will suspend callback didCompleteWithError: method, this time there are three cases: normal completion, the user cancel, abort, etc., according to the error can be judged separately.

Complete code:
https://github.com/dolacmeng/HYDownLoader

Published 103 original articles · won praise 55 · views 380 000 +

Guess you like

Origin blog.csdn.net/dolacmeng/article/details/88686286