iOS之断点下载,使用NSURLSession简单封装
最近公司需要做个文件管理的功能模块,刚交到博主手上时,头都大了。因为没做过这方面的东西,只好咬牙加班,并请教某位大神,指点了一下,清楚研究方向,找了网上大量资料,最后实现简单的封装。
上代码:.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简单封装的更多相关文章
- iOS 学习 - 10下载(4) NSURLSession 会话 篇
NSURLConnection通过全局状态来管理cookies.认证信息等公共资源,这样如果遇到两个连接需要使用不同的资源配置情况时就无法解决了,但是这个问题在NSURLSession中得到了解决.N ...
- iOS 学习 - 10下载(3) NSURLSession 音乐 篇
使用 NSURLSession 下载,需要注意的是文件下载文件之后会自动保存到一个临时目录,需要开发人员自己将此文件重新放到其他指定的目录中 // // ViewController.m // Web ...
- iOS 学习 - 10下载(2) NSURLSession 图片 篇
使用NSURLSessionDownloadTask下载文件的过程与前面差不多,需要注意的是文件下载文件之后会自动保存到一个临时目录,需要开发人员自己将此文件重新放到其他指定的目录中. // // V ...
- iOS中 断点下载详解 韩俊强的博客
布局如下: 基本拖拉属性: #import "ViewController.h" #import "AFNetworking.h" @interface Vie ...
- iOS开发——网络篇——NSURLSession,下载、上传代理方法,利用NSURLSession断点下载,AFN基本使用,网络检测,NSURLConnection补充
一.NSURLConnection补充 前面提到的NSURLConnection有些知识点需要补充 NSURLConnectionDataDelegate的代理方法有一下几个 - (void)conn ...
- iOS开发 -------- AFNetworking实现简单的断点下载
一 实现如下效果 二 实现代码 // // ViewController.m // AFNetworking实现断点下载 // // Created by lovestarfish on 15/1 ...
- iOS 大文件断点下载
iOS 在下载大文件的时候,可能会因为网络或者人为等原因,使得下载中断,那么如何能够进行断点下载呢? // resumeData的文件路径 #define XMGResumeDataFile [[NS ...
- iOS开发网络请求——大文件的多线程断点下载
iOS开发中网络请求技术已经是移动app必备技术,而网络中文件传输就是其中重点了.网络文件传输对移动客户端而言主要分为文件的上传和下载.作为开发者从技术角度会将文件分为小文件和大文件.小文件因为文件大 ...
- ios网络 -- HTTP请求 and 文件下载/断点下载
一:请求 http://www.jianshu.com/p/8a90aa6bad6b 二:下载 iOS网络--『文件下载.断点下载』的实现(一):NSURLConnection http://www. ...
随机推荐
- 第1章 网络编程基础(2)——Socket编程原理
Socket编程原理 Socket是网络通信端点的一种抽象,它提供了一种发送和接收数据的机制. 流socket(SOCK_STREAM):双向.有序.无重复.并且无记录边界 数据报Socket(SOC ...
- 对于js原型和原型链继承的简单理解(第三种,复制继承)
复制继承:简单理解,就是把父对象上的所有属性复制到自身对象上: function Cat(){ this.climb = function(){ alert("我会爬树"); } ...
- jquery easyui+layer后台框架
最近使用jquery easyui搭建了一个后台框架,以方便以后使用 上图先: 下载地址:CSDN下载
- MySQL STRAIGHT_JOIN
问题 最近在调试一条查询耗时5s多的sql语句,这条sql语句用到了多表关联(inner join),按时间字段排序(order by),时间字段上已经创建了索引(索引名IDX_published_a ...
- angularjs中{{}} 加载出现闪烁问题
在head标签中加入 [ng-cloak] { display: none !important; } 在页面的body标签上添加 ng-cloak 可以解决页面上先后加载闪烁问题
- IOS内存nil与release的区别
IOS内存nil与release的区别 分类: IOS内存管理 nil和release的作用: nil就是把一个对象的指针置为空,只是切断了指针与内存中对象的联系:而release才是真正通知 ...
- ASIHTTPRequest中的DELETE、PUT、GET、POST请求实例-备用
感谢分享 // ASIFormDataRequestTests.m // Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRe ...
- 安装Visual Studio 2010之后怎样安装MSDN Library
这篇博客参考自:http://justargon.blog.163.com/blog/static/21394413020134100737688/ MSDN2010安装及使用(MSDN Librar ...
- (摘)DataGuard物理standby管理 - 主备切换
DataGuard物理standby管理 - 主备切换 Dataguard的切换分为两种,switchover和failover. switchover一般用于数据库或硬件升级,这时只需要较短时间中断 ...
- [转]Ubuntu Linux 安装 .7z 解压和压缩文件
原文网址:http://blog.csdn.net/zqlovlg/article/details/8033456 安装方法: sudo apt-get install p7zip-full 解压文件 ...