NSURLSession实现续点下载

#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 (^)(long long totalReceivedContentLength, long long totalContentLength))progress;
/**
 *  启动断点续传下载请求
 */
-(void)start;

/**
 *  取消断点续传下载请求
 */
-(void)cancel;
@end
#import "MQLResumeManager.h"

typedef void (^completionBlock)();
typedef void (^progressBlock)();

@interface MQLResumeManager ()<NSURLSessionDelegate, NSURLSessionTaskDelegate>

@property (nonatomic, strong) NSURLSession *session;    //注意一个session只能有一个请求任务
@property(nonatomic, readwrite, retain) NSError *error; //请求出错
@property(nonatomic, readwrite, copy) completionBlock completionBlock;
@property(nonatomic, readwrite, copy) progressBlock progressBlock;

@property (nonatomic, strong) NSURL *url;           //文件资源地址
@property (nonatomic, strong) NSString *targetPath; //文件存放路径
@property long long totalContentLength;             //文件总大小
@property long long totalReceivedContentLength;     //已下载大小

/**
 *  设置成功、失败回调block
 *
 *  @param success 成功回调block
 *  @param failure 失败回调block
 */
- (void)setCompletionBlockWithSuccess:(void (^)())success
                              failure:(void (^)(NSError *error))failure;

/**
 *  设置进度回调block
 *
 *  @param progress
 */
-(void)setProgressBlockWithProgress:(void (^)(long long totalReceivedContentLength, long long totalContentLength))progress;

/**
 *  获取文件大小
 *  @param path 文件路径
 *  @return 文件大小
 *
 */
- (long long)fileSizeForPath:(NSString *)path;

@end

@implementation MQLResumeManager

/**
 *  设置成功、失败回调block
 *
 *  @param success 成功回调block
 *  @param failure 失败回调block
 */
- (void)setCompletionBlockWithSuccess:(void (^)())success
                              failure:(void (^)(NSError *error))failure{

    __weak typeof(self) weakSelf = self;
    self.completionBlock = ^ {

        dispatch_async(dispatch_get_main_queue(), ^{

            if (weakSelf.error) {
                if (failure) {
                    failure(weakSelf.error);
                }
            } else {
                if (success) {
                    success();
                }
            }

        });
    };
}

/**
 *  设置进度回调block
 *
 *  @param progress
 */
-(void)setProgressBlockWithProgress:(void (^)(long long totalReceivedContentLength, long long totalContentLength))progress{

    __weak typeof(self) weakSelf = self;
    self.progressBlock = ^{

        dispatch_async(dispatch_get_main_queue(), ^{

            progress(weakSelf.totalReceivedContentLength, weakSelf.totalContentLength);
        });
    };
}

/**
 *  获取文件大小
 *  @param path 文件路径
 *  @return 文件大小
 *
 */
- (long long)fileSizeForPath:(NSString *)path {

    long long fileSize = 0;
    NSFileManager *fileManager = [NSFileManager new]; // not thread safe
    if ([fileManager fileExistsAtPath:path]) {
        NSError *error = nil;
        NSDictionary *fileDict = [fileManager attributesOfItemAtPath:path error:&error];
        if (!error && fileDict) {
            fileSize = [fileDict fileSize];
        }
    }
    return fileSize;
}

/**
 *  创建断点续传管理对象,启动下载请求
 *  @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 (^)(long long totalReceivedContentLength, long long totalContentLength))progress{

    MQLResumeManager *manager = [[MQLResumeManager alloc]init];

    manager.url = url;
    manager.targetPath = targetPath;
    [manager setCompletionBlockWithSuccess:success failure:failure];
    [manager setProgressBlockWithProgress:progress];

    manager.totalContentLength = 0;
    manager.totalReceivedContentLength = 0;

    return manager;
}

/**
 *  启动断点续传下载请求
 */
-(void)start{

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:self.url];

    long long downloadedBytes = self.totalReceivedContentLength = [self fileSizeForPath:self.targetPath];
    if (downloadedBytes > 0) {

        NSString *requestRange = [NSString stringWithFormat:@"bytes=%llu-", downloadedBytes];
        [request setValue:requestRange forHTTPHeaderField:@"Range"];
    }else{

        int fileDescriptor = open([self.targetPath UTF8String], O_CREAT | O_EXCL | O_RDWR, 0666);
        if (fileDescriptor > 0) {
            close(fileDescriptor);
        }
    }

    NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    self.session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:queue];

    NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];
    [dataTask resume];
}

/**
 *  取消断点续传下载请求
 */
-(void)cancel{

    if (self.session) {

        [self.session invalidateAndCancel];
        self.session = nil;
    }
}

#pragma mark -- NSURLSessionDelegate
/* The last message a session delegate receives.  A session will only become
 * invalid because of a systemic error or when it has been
 * explicitly invalidated, in which case the error parameter will be nil.
 */
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error{

    NSLog(@"didBecomeInvalidWithError");
}

#pragma mark -- NSURLSessionTaskDelegate
/* Sent as the last message related to a specific task.  Error may be
 * nil, which implies that no error occurred and this task is complete.
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error{

    NSLog(@"didCompleteWithError");

    if (error == nil && self.error == nil) {

        self.completionBlock();

    }else if (error != nil){

        if (error.code != -999) {

            self.error = error;
            self.completionBlock();
        }

    }else if (self.error != nil){

        self.completionBlock();
    }


}

#pragma mark -- NSURLSessionDataDelegate
/* Sent when data is available for the delegate to consume.  It is
 * assumed that the delegate will retain and not copy the data.  As
 * the data may be discontiguous, you should use
 * [NSData enumerateByteRangesUsingBlock:] to access it.
 */
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data{

    //根据status code的不同,做相应的处理
    NSHTTPURLResponse *response = (NSHTTPURLResponse*)dataTask.response;
    if (response.statusCode == 200) {

        self.totalContentLength = dataTask.countOfBytesExpectedToReceive;

    }else if (response.statusCode == 206){

        NSString *contentRange = [response.allHeaderFields valueForKey:@"Content-Range"];
        if ([contentRange hasPrefix:@"bytes"]) {
            NSArray *bytes = [contentRange componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" -/"]];
            if ([bytes count] == 4) {
                self.totalContentLength = [[bytes objectAtIndex:3] longLongValue];
            }
        }
    }else if (response.statusCode == 416){

        NSString *contentRange = [response.allHeaderFields valueForKey:@"Content-Range"];
        if ([contentRange hasPrefix:@"bytes"]) {
            NSArray *bytes = [contentRange componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" -/"]];
            if ([bytes count] == 3) {

                self.totalContentLength = [[bytes objectAtIndex:2] longLongValue];
                if (self.totalReceivedContentLength == self.totalContentLength) {

                    //说明已下完

                    //更新进度
                    self.progressBlock();
                }else{

                    //416 Requested Range Not Satisfiable
                    self.error = [[NSError alloc]initWithDomain:[self.url absoluteString] code:416 userInfo:response.allHeaderFields];
                }
            }
        }
        return;
    }else{

        //其他情况还没发现
        return;
    }

    //向文件追加数据
    NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:self.targetPath];
    [fileHandle seekToEndOfFile]; //将节点跳到文件的末尾

    [fileHandle writeData:data];//追加写入数据
    [fileHandle closeFile];

    //更新进度
    self.totalReceivedContentLength += data.length;
    self.progressBlock();
}
@end

在控制器里面实现续点下载

#import "ViewController.h"
#import "MQLResumeManager.h"

@interface ViewController ()

@property (nonatomic, strong) MQLResumeManager *manager;

@property (nonatomic, weak) IBOutlet UIImageView *imageWithBlock;
@property (nonatomic, strong) NSString *targetPath;

@property (nonatomic, weak) IBOutlet UIProgressView *progressView;
@property (nonatomic, weak) IBOutlet UILabel *lab;
@property (nonatomic, weak) IBOutlet UIButton *deleteBtn;


/**
 *  简单请求
 *  @param sender
 */
-(IBAction)simpleRequest:(id)sender;

/**
 *  取消请求
 *  @param sender
 */
-(IBAction)cancelRequest:(id)sender;

/**
 *  删除文件
 *  @param sender
 */
-(IBAction)deleteImage:(id)sender;


@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}


/**
 *  简单请求
 *  @param sender
 */
-(IBAction)simpleRequest:(id)sender{

    //1.准备
    if (self.manager) {

        [self cancelRequest:nil];
    }

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory=[paths objectAtIndex:0];//Documents目录
    self.targetPath = [documentsDirectory stringByAppendingPathComponent:@"myPic"];

//    NSURL *url = [NSURL URLWithString:@"http://p1.pichost.me/i/40/1639665.png"];
    NSURL *url = [NSURL URLWithString:@"http://p1.pichost.me/i/40/1639665.png"];


    self.manager = [MQLResumeManager resumeManagerWithURL:url targetPath:self.targetPath success:^{
        NSLog(@"success");
        self.imageWithBlock.image = [UIImage imageWithContentsOfFile:self.targetPath];
        self.deleteBtn.hidden = NO;

    } failure:^(NSError *error) {

        NSLog(@"failure");

    } progress:^(long long totalReceivedContentLength, long long totalContentLength) {

        float percent = 1.0 * totalReceivedContentLength / totalContentLength;
        NSString *strPersent = [[NSString alloc]initWithFormat:@"%.f", percent *100];
        self.progressView.progress = percent;
        self.lab.text = [NSString stringWithFormat:@"已下载%@%%", strPersent];
    }];

    //2.启动
    [self.manager start];

}

/**
 *  取消请求
 *  @param sender
 */
-(IBAction)cancelRequest:(id)sender{

    [self.manager cancel];
    self.manager = nil;
}

/**
 *  删除文件
 *
 *  @param sender
 */
-(IBAction)deleteImage:(id)sender{
    NSFileManager *manager = [NSFileManager defaultManager];
    NSError *error = nil;
    [manager removeItemAtPath:self.targetPath error:&error];

    if (error == nil) {
        self.imageWithBlock.image = [UIImage imageWithContentsOfFile:self.targetPath];
        self.progressView.progress = 0;
        self.lab.text = nil;
        self.deleteBtn.hidden = YES;
    }
}
@end

这样就实现续点下载的操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值