最近公司需要做个文件管理的功能模块,刚交到博主手上时,头都大了。因为没做过这方面的东西,只好咬牙加班,并请教某位大神,指点了一下,清楚研究方向,找了网上大量资料,最后实现简单的封装。

上代码:.h文件

#import <Foundation/Foundation.h>

@interface DocDownloader : NSObject

/**
* 创建断点续传管理对象,启动下载请求
*
* @param url 文件资源地址
* @param targetPath 文件存放路径
* @param success 文件下载成功的回调块
* @param failure 文件下载失败的回调块
* @param progress 文件下载进度的回调块
*
* @return 断点续传管理对象
*
*/
+(DocDownloader*)resumeManagerWithURL:(NSURL*)url
targetPath:(NSString*)targetPath
success:(void (^)())success
failure:(void (^)(NSError *error))failure
progress:(void (^)(long long totalReceivedContentLength, long long totalContentLength))progress; /**
* 启动断点续传下载请求(普通的静态下载链接或GET请求)
*/
-(void)start; /**
* 启动断点续传下载请求(POST请求)
*
* @param params POST的内容
*/
-(void)startWithParams:(NSString *)params; /**
* 取消断点续传下载请求
*/
-(void)cancel;

.m文件

#import "DocDownloader.h"

typedef void (^completionBlock)();
typedef void (^progressBlock)(); @interface DocDownloader ()<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 DocDownloader /**
* 设置成功、失败回调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 = ;
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 断点续传管理对象
*
*/
+(DocDownloader*)resumeManagerWithURL:(NSURL*)url
targetPath:(NSString*)targetPath
success:(void (^)())success
failure:(void (^)(NSError *error))failure
progress:(void (^)(long long totalReceivedContentLength, long long totalContentLength))progress{ DocDownloader *manager = [[DocDownloader alloc]init]; manager.url = url;
manager.targetPath = targetPath;
[manager setCompletionBlockWithSuccess:success failure:failure];
[manager setProgressBlockWithProgress:progress]; manager.totalContentLength = ;
manager.totalReceivedContentLength = ; return manager;
} /**
* 启动断点续传下载请求(普通的静态下载链接或GET请求)
*/
-(void)start{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:self.url];
long long downloadedBytes = self.totalReceivedContentLength = [self fileSizeForPath:self.targetPath];
if (downloadedBytes > ) {
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, );
if (fileDescriptor > ) {
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];
} /**
* 启动断点续传下载请求(POST请求)
*
* @param params POST的内容
*/
-(void)startWithParams:(NSString *)params{
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:self.url];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:[params dataUsingEncoding:NSUTF8StringEncoding]];
long long downloadedBytes = self.totalReceivedContentLength = [self fileSizeForPath:self.targetPath];
if (downloadedBytes > ) { 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, );
if (fileDescriptor > ) {
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 != -) {
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{
//NSLog(@"dataLength = %lu",(unsigned long)data.length);
//根据status code的不同,做相应的处理
NSHTTPURLResponse *response = (NSHTTPURLResponse*)dataTask.response;
NSLog(@"response = %@",response);
if (response.statusCode == ) {
NSString *contentRange = [response.allHeaderFields valueForKey:@"Content-Length"];
self.totalContentLength = [contentRange longLongValue];
}else if (response.statusCode == ){
NSString *contentRange = [response.allHeaderFields valueForKey:@"Content-Range"];
if ([contentRange hasPrefix:@"bytes"]) {
NSArray *bytes = [contentRange componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" -/"]];
if ([bytes count] == ) {
self.totalContentLength = [[bytes objectAtIndex:] longLongValue];
}
}
}else if (response.statusCode == ){
NSString *contentRange = [response.allHeaderFields valueForKey:@"Content-Range"];
if ([contentRange hasPrefix:@"bytes"]) {
NSArray *bytes = [contentRange componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" -/"]];
if ([bytes count] == ) {
self.totalContentLength = [[bytes objectAtIndex:] longLongValue];
if (self.totalReceivedContentLength == self.totalContentLength) {
//说明已下完,更新进度
self.progressBlock();
}else{
//416 Requested Range Not Satisfiable
self.error = [[NSError alloc]initWithDomain:[self.url absoluteString] code: userInfo:response.allHeaderFields];
}
}
}
return;
}else{
//其他情况还没发现
return;
} NSFileManager *fileManager = [NSFileManager defaultManager];
//向文件追加数据
NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:self.targetPath];
[fileHandle seekToEndOfFile]; //将节点跳到文件的末尾
[fileHandle writeData:data];//追加写入数据
if ([fileManager fileExistsAtPath:self.targetPath]) {
self.totalReceivedContentLength = [[fileManager attributesOfItemAtPath:self.targetPath error:nil] fileSize];
if (self.totalContentLength == self.totalReceivedContentLength) {
NSLog(@"下载完了");
//下载完了,停止请求
[self cancel];
self.completionBlock();
}
}
[fileHandle closeFile];
self.progressBlock();
}

使用步骤:

1.

 [DocDownloader resumeManagerWithURL:[NSURL URLWithString:urlStr] targetPath:self.targetPath success:^{
NSLog(@"WebRequestTypeDocDownload_success");
//下载完成,可以写入一些完成之后的操作
} failure:^(NSError *error) {
NSLog(@"WebRequestTypeDocDownload_failure");
//下载失败,可以做相应的提示
} progress:^(long long totalReceivedContentLength, long long totalContentLength) {
//回调totalReceivedContentLength和totalContentLength
// 下载了多少:totalReceivedContentLength
// 文件总大小: totalContentLength
// 进度条可以这样表示:
//progress = totalReceivedContentLength / totalContentLength
}];

2.启动下载(POST请求)

 [self.manager startWithParams:paramStr];

3.暂停下载

- (void)suspendWithCancel {

    [self.manager cancel];

    self.manager = nil;

}

那么问题来了,如果下载了一部分就暂停了,退出app,重新进来,文件数据呢???

这个其实我们已经写入文件了,最好写入Documents目录下。首先判断第一次进入时,检查文件路径是否存在,若存在就需要计算出文件大小,并与我们知道的文件的总大小做比较。

这里比较,可以分为两种情况:(这里以fileSize为文件计算出的大小,totalFileSize为文件的总大小)

第一种文件没有加密,这个很好处理,1.如果0<fileSize<totalFileSize,说明已经下载了一部分,下载进度为fileSize。2.如果fileSize==totalFileSize,说明已经下载完了,标记状态。

第二种文件下载完成后加密,加密之后的文件大小会比原来的大小大一些(由于博主的加密方式是sm4加密,不知道其他的加密方法会不会比原来的大一些),

1.如果0<fileSize<totalFileSize,说明已经下载了一部分,下载进度为fileSize。2.如果fileSize>=totalFileSize,说明已经下载完了,标记状态。

iOS之断点下载,使用NSURLSession简单封装的更多相关文章

  1. iOS 学习 - 10下载(4) NSURLSession 会话 篇

    NSURLConnection通过全局状态来管理cookies.认证信息等公共资源,这样如果遇到两个连接需要使用不同的资源配置情况时就无法解决了,但是这个问题在NSURLSession中得到了解决.N ...

  2. iOS 学习 - 10下载(3) NSURLSession 音乐 篇

    使用 NSURLSession 下载,需要注意的是文件下载文件之后会自动保存到一个临时目录,需要开发人员自己将此文件重新放到其他指定的目录中 // // ViewController.m // Web ...

  3. iOS 学习 - 10下载(2) NSURLSession 图片 篇

    使用NSURLSessionDownloadTask下载文件的过程与前面差不多,需要注意的是文件下载文件之后会自动保存到一个临时目录,需要开发人员自己将此文件重新放到其他指定的目录中. // // V ...

  4. iOS中 断点下载详解 韩俊强的博客

    布局如下: 基本拖拉属性: #import "ViewController.h" #import "AFNetworking.h" @interface Vie ...

  5. iOS开发——网络篇——NSURLSession,下载、上传代理方法,利用NSURLSession断点下载,AFN基本使用,网络检测,NSURLConnection补充

    一.NSURLConnection补充 前面提到的NSURLConnection有些知识点需要补充 NSURLConnectionDataDelegate的代理方法有一下几个 - (void)conn ...

  6. iOS开发 -------- AFNetworking实现简单的断点下载

    一 实现如下效果   二 实现代码 // // ViewController.m // AFNetworking实现断点下载 // // Created by lovestarfish on 15/1 ...

  7. iOS 大文件断点下载

    iOS 在下载大文件的时候,可能会因为网络或者人为等原因,使得下载中断,那么如何能够进行断点下载呢? // resumeData的文件路径 #define XMGResumeDataFile [[NS ...

  8. iOS开发网络请求——大文件的多线程断点下载

    iOS开发中网络请求技术已经是移动app必备技术,而网络中文件传输就是其中重点了.网络文件传输对移动客户端而言主要分为文件的上传和下载.作为开发者从技术角度会将文件分为小文件和大文件.小文件因为文件大 ...

  9. ios网络 -- HTTP请求 and 文件下载/断点下载

    一:请求 http://www.jianshu.com/p/8a90aa6bad6b 二:下载 iOS网络--『文件下载.断点下载』的实现(一):NSURLConnection http://www. ...

随机推荐

  1. PhoneGap移动开发框架

    phonegap是一个跨平台的移动app开发框架,可以把html css js写的页面打包成跨平台的可以安装的移动app,并且可以调用原生的几乎所有的功能,比如摄像头,联系人,加速度等    看到一篇 ...

  2. 使用UISegementControl实现简易汤姆猫程序

    // // TomViewController.m #import "TomViewController.h" #import <AVFoundation/AVFoundat ...

  3. js引用类型姿势

    栈 1)var a=new Array(),a.push(a,b,...),a.pop() queue 1)var a=new Array(), a.push(a,b,...),a.shift() a ...

  4. window下Slik SVN的安装配置

    我相信各位都应该对SVN不会陌生吧,我相信绝大多数人都使用过,但是并不是人人都自己配置过SVN服务器.下面就是我配置SVN服务器的步骤,以及在配置过程中碰见的一些问题,在此记录,希望对你有所帮助. 安 ...

  5. win7安装memcached

    根据公司业务需求,需要用memcache缓存,正好接触一下,在win7下配置安装: 1. 下载memcache的windows稳定版,解压放某个盘下面,比如在c:\memcached 2. 在终端(也 ...

  6. php中计算中文字符串长度、截取中文字符串

    在做PHP开发的时候,由于我国的语言环境问题,所以我们常常需要对中文进行处理.在PHP中,我们都知道有专门的mb_substr和mb_strlen函数,可以对中文进行截取和计算长度,但是,由于这些函数 ...

  7. 二维指针*(void **)的研究(uC/OS-II案例) 《转载》

    uC/OS-II内存管理函数内最难理解的部分就是二维指针,本文以图文并茂的方式对二维指针进行了详细分析与讲解.看完本文,相信对C里面指针的概念又会有进一步的认识. 一.OSMemCreate( ) 函 ...

  8. arm get_vector_swi_address

    unsigned long* get_vector_swi_addr() { const void *swi_addr = 0xFFFF0008; unsigned ; unsigned ; unsi ...

  9. 摘录 javescript 常用函数

     

  10. repo sync 时的自动续接脚本[转]

    按理说在repo init  ....之后使用repo sync就可以开始下载源码了,但是在下载过程中经常会出现没网速“死”的情况.当然,我修改了/etc/hosts文件之后就再也么有死过.在没网速提 ...