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. ...
随机推荐
- Xcode使用xib拖线时出现: could not insert new outlet connection
解决方法: 1.在新建类的时候没有选择将这个类加入到对应的"Target"中. 2.重新将文件加入项目 操作步骤就是选中出问题的.m和.h文件,点删除键,然后选"Remo ...
- iOS_SN_CocoaPods使用详细说明( 转)
一.概要 iOS开发时,项目中会引用许多第三方库,CocoaPods(https://github.com/CocoaPods/CocoaPods)可以用来方便的统一管理这些第三方库. 二.安装 由于 ...
- Enumeration
Interface Enumeration<E> hasMoreElements() boolean hasMoreElements() 仅当此枚举对象包含至少一个以上元素为真:否则 ...
- C++程序设计教程学习(1)-第一部分 编程基础
第一章 概述 C++到底难不难学?没有学不会的事情 1.1 程序设计语言 语言 编程语言 人和计算机交流的工具,群体扩大,人人间交流过程描述与信息表达的工具 机器语言,汇编语言,高级语言 1.2 C+ ...
- 获取aplicationContext对象,从而获取任何注入的对象
在Servlet中 方法一: 从ServletContext 取出 Spring容器上下文 ApplicationContext applicationContext = (ApplicationCo ...
- 使用PHP-Barcode轻松生成条形码(一)
最近由于工作需要,研究了一下PHP如何生成条形码.虽然二维码时下比较流行,但是条形码依然应用广泛,不可替代.园子里有很多讲利用PHP生成条形码的文章,基本上都是围绕Barcode Bakery的,它虽 ...
- discuz 使模板中的函数不解析 正常使用
<!--{if $_GET['zcdw']=="baxi"}--><!--{eval $duiwuxinxi = "巴西队";}-->& ...
- JAVA回调函数ANDROID中典型的回调地方
在计算机中回调函数是指通过函数参数传递到其他代码类的,某一块可执行代码的引用,这以设计允许了底层代码调用者在高层定义的子程序. 在JAVA里面我们使用接口的方式来实现函数的回调. 回调的通俗就是:程序 ...
- 安卓仿制新浪微博(一)之OAuth2授权接口
这里需要用到请求授权(authorize)以及获取授权(access_token) 第一步: 将新浪的sdk放在src/libs下面 二: //创建方法实现authorize public void ...
- 安装Hadoop集群的最快的软件
Quick Hadoop是一款安装Hadoop集群的桌面软件,只需要点两下鼠标,一分钟之内安装Hadoop到集群上,超快! 还在每台主机的Shell里一行一行地敲安装Hadoop的命令?别苦逼了! 用 ...