说明:
- 本文将介绍使用NSURLSessionDownloadDelegate的代理方法实现显示加载进度和断点续传(网络中断情况, 不是退出程序情况. 只要不是退出程序的情况下, 下载失败都可以用本文的方法实现断点续传).
实现功能的方法并不唯一, 大家要灵活使用.
session 类型 : default session.
session的创建方法: + sessionWithConfiguration:delegate:delegateQueue:
task 类型: download task.
代码中所使用到的接口(例:
http://2b2726fb22467.cdn.sohucs.com/55fbb97f13aa9.mp4) 都具有时效性, 建议测试时使用一个可以提供数据的新接口.请确保Xcode版本在6.4以上.
文章中尽量不使用或少使用封装, 目的是让大家清楚为了实现功能所需要的官方核心API是哪些(如果使用封装, 会在封装外面加以注释)
此文章由 @春雨 编写. 经 @Scott,@黑子 审核. 若转载此文章,请注明出处和作者
iOS_断点续传
核心API
Class :
- NSURLSession
- NSURLSessionConfiguration
- NSURLSessionTask
- NSURLSessionDownload
Delegate :
- NSURLSessionDelegate
- NSURLSessionTaskDelegate
- NSURLSessionDownloadDelegate
涉及的API :
- NSURLSession
/** 创建NSURLSession对象的类方法. */
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(id<NSURLSessionDelegate>)delegate delegateQueue:(NSOperationQueue *)queue
/** 创建一个download任务, 用于恢复之前取消或者失败的download任务. */
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
/** 创建一个download任务, 用于恢复之前取消或者失败的download任务. 并回调Block块. */
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler
/** 创建一个download任务, 用于恢复之前取消或者失败的download任务. 并带有一个处理任务完成的block块. */
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler
- NSURLSessionDownloadTask
/** 取消download任务, 并回调一个带续传数据的Block块. */
- (void)cancelByProducingResumeData:(void (^)(NSData *resumeData))completionHandler
- NSURLSessionTask
/** 取消任务. */
- (void)cancel
- NSURLSessionDownloadDelegate
/** 告诉delegate, download task已经完成. */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
/** 定期通知代理人下载进度. */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
/** 告诉代理人下载任务已经恢复. */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
- NSURLSessionTaskDelegate
/** 告诉delegate, task已经完成. */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
- NSURLSessionDownloadTask
/** 取消下载并调用回调与恢复数据供以后使用. */
- (void)cancelByProducingResumeData:(void (^)(NSData *resumeData))completionHandler
功能实现
思路 :
- 1 . 下载过程中显示加载进度.
- 2 . 断点续传(网络中断)
注: 判断网络状态.(用于测试) - 4 . 取消下载.
- 5 . 其他方法.
Code :
1. 加载进度 和 2. 断点续传(网络中断)
注: 网络判断部分请查看iOS_AFNetworking_AFNetworkReachabilityManager(检测网络可达性)
#import "ViewController.h"
#import "AFNetworkReachabilityManager.h"
@interface ViewController ()<NSURLSessionDelegate, NSURLSessionDownloadDelegate, NSURLSessionTaskDelegate>
@property (nonatomic, strong) NSURLSessionDownloadTask *downloadTask; // 下载任务
@property (nonatomic, strong) NSURLSession *session; // 会话
@property (nonatomic, strong) NSData *resumData; // 续传数据
@property (nonatomic, strong) NSDate *lastDate; // 上次获取到数据的时间.
/** 两个label都是用Storybord创建的. */
@property (weak, nonatomic) IBOutlet UILabel *progressLabel;
@property (weak, nonatomic) IBOutlet UILabel *speedLabel;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self networkState];
[self handleData];
}
- (void)handleData
{
/** 创建默认配置的会话. */
self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];/**< 注意: 我们设置了主线程. */
/** 创建下载任务. */
self.downloadTask = [_session downloadTaskWithURL:[NSURL URLWithString:@"http://2b2726fb22467.cdn.sohucs.com/55fbb97f13aa9.mp4"]];
/** 开启下载任务. */
[_downloadTask resume];
/** 记载开始下载的时间. */
self.lastDate = [NSDate date];
}
/** NSURLSessionDownloadDeleget 方法.
* bytesWritten 参数: 自上次调用该委托方法之后,传输的字节数。
* totalBytesWritten 参数: 已经传输的总字节数.
* totalBytesExpectedToWrite 参数: 应该传输的字节数(相当于下载数据的总字节数).
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
/** 这次获取字节数的时间. */
NSDate *currentDate = [NSDate date];
/** 两次获取字节数的时间间隔. */
NSTimeInterval time =[currentDate timeIntervalSinceDate:_lastDate];
/** 时间间隔大于1秒, 再更新UI. */
if (time >= 1) {
/** 算出下载速度. */
NSString *speedString = [NSString stringWithFormat:@"%.2lfKB", bytesWritten / time / 1024];
/** 更新UI.(注意的部分请看 progressLabel.text的注.) */
_speedLabel.text = speedString;
/** 将这次的时间赋值给lastData属性. */
_lastDate = currentDate;
}
/** 求当前的加载进度. */
float progress = (float)totalBytesWritten / totalBytesExpectedToWrite;
NSString *str = [NSString stringWithFormat:@"%.2f%%", progress * 100];
/** 注:
* 在创建session时, 我们给delegateQueue设置的是主线程, 所以我们可以直接给progressLabeL的text赋值.
* 但是, 如果设置的不是主线程或者是使用completionHandler块下载数据, 那么我们应该回到主线程更新UI.
* 原因: delegateQueue参数的作用是给代理方法提供一个执行的队列, 这个队列跟下载数据没有关系, 只是说代理方法应该在这个队列执行. 如果delegateQueue是nil或者没有delegateQueue, 那么系统会自动创建一系列的队列, 让代理方法或者completionHandler块, 在这些队列里执行.
*/
_progressLabel.text = str;
}
/** NSURLSessionDownloadDeleget 方法.
* location 参数: 下载完成的数据存储的位置.
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
/** 取出Library文件的路径. */
NSString *saveString = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).lastObject;
/** 取出"55fbb97f13aa9.mp4" 字符串. */
NSString *fileName = [NSURL URLWithString:@"http://2b2726fb22467.cdn.sohucs.com/55fbb97f13aa9.mp4"].lastPathComponent;
/** 拼接成保存路径. */
NSString *saveURL = [NSString stringWithFormat:@"%@/%@", saveString, fileName];
/** 将下载好的数据复制到波哦村路径, 避免丢失. */
NSError *error = nil;
[[NSFileManager defaultManager] copyItemAtURL:location toURL:[NSURL fileURLWithPath:saveURL] error:&error];
}
/** NSURLSessionTaskDelegate 方法
* 任务完成调用.(无论何种任务完成, 都会调用这个方法.)
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error) {
/** 如果发生错误, 我们可以从error中获取到续传数据. */
self.resumData = [error.userInfo objectForKey:NSURLSessionDownloadTaskResumeData];
NSLog(@"download error: %@", error);
} else {
NSLog(@"下载成功!");
}
}
/** 对网络的判断. */
- (void)networkState
{
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusNotReachable:
NSLog(@"无网!");
break;
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(@"WIFI");
if (_resumData) {
/** 如果resumData属性有值, 就重新创建一个download任务. */
��A NSURLSessionDownloadTask *resumeDownloadTask = [_session downloadTaskWithResumeData:_resumData];
/** 也可以使用 downloadTaskWithResumeData:completionHandler: 方法创建. */
[resumeDownloadTask resume];
}
break;
default:
break;
}
}];
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
}
/** NSURLSessionDownloadDeleget 方法.
* 当使用 downloadTaskWithResumeData: 或者 downloadTaskWithResumeData:completionHandler: 方法创建download类型的任务时, 就会调用这个代理方法.
* 之后跟正常的download任务都一样.
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
/** 目前笔者还不知道在这个方法内需要执行哪些操作, 所以只是先输出一下. */
NSLog(@"download继续加载!");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
注:
可以将 ��A 替换成以下代码, 在completionHandler的block块中处理接收到的数据.
NSURLSessionDownloadTask *secondTask = [_session downloadTaskWithResumeData:self.resumData completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
// 当secondTask 完成时, 为了下载的文件不丢失, 需要将文件复制到另外的路径.
NSString *fileName = [NSURL URLWithString:VIDEOURL].lastPathComponent;
NSString *tempPath = [NSString stringWithFormat:@"%@/Documents/%@", NSHomeDirectory(), fileName];
NSError *fileError = nil;
[[NSFileManager defaultManager] copyItemAtURL:location toURL:[NSURL fileURLWithPath:tempPath] error:&fileError];
}];
4 . 取消任务.
当执行下列代码时, 就可以取消任务, 并且可以获取到已经接收到的数据, 用于以后恢复下载.
[self.downloadTast cancelByProducingResumeData:^(NSData *resumeData) {
_resumData = resumeData;
}];
恢复下载任务的步骤和上面的方法相同.
如果以后不用恢复下载, 可以直接取消
[self.downloadTast cancel];
5 . 其他方法.
可以使用NSURLSessionTask的countOfBytesReceived 和 countOfBytesExpectedToReceive 属性计算加载进度. 还有可以用 countOfBytesSent 和 countOfBytesExpectedToSend 属性计算upload 任务.
(Demo的下载链接)[]
API 官方注释
- NSURLSession
/**
* @brief Creates a download task to resume a previously canceled or failed download.
*
* @param <resumeData> A data object that provides the data necessary to resume a download.
*
* @return The new session download task.
*/
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
/**
* @brief Creates a download task to resume a previously canceled or failed download and calls a handler upon completion.
*
* @param <resumeData> A data object that provides the data necessary to resume the download.
*
* @param <completionHandler> The completion handler to call when the load request is complete. This handler is executed on the delegate queue.
Unless you have provided a custom delegate, this parameter must not be nil, because there is no other way to retrieve the response data.
*
* @return The new session download task.
*/
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler
/**
* @brief Tells the delegate that the download task has resumed downloading.
*
* @param <session> The session containing the download task that finished.
*
* @param <downloadTask> The download task that resumed. See explanation in the discussion.
*
* @param <fileOffset> If the file's cache policy or last modified date prevents reuse of the existing content, then this value is zero. Otherwise, this value is an integer representing the number of bytes on disk that do not need to be retrieved again.
* @param <expectedTotalBytes> The expected length of the file, as provided by the Content-Length header. If this header was not provided, the value is NSURLSessionTransferSizeUnknown.
*/
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData completionHandler:(void (^)(NSURL *location, NSURLResponse *response, NSError *error))completionHandler
- NSURLSessionDownloadTask
/**
* @brief Creates a download task to resume a previously canceled or failed download.
*
* @param <completionHandler> A completion handler that is called when the download has been successfully canceled.
If the download is resumable, the completion handler is provided with a resumeData object. Your app can later pass this object to a session’s downloadTaskWithResumeData: or downloadTaskWithResumeData:completionHandler: method to create a new task that resumes the download where it left off.
*
*/
- (void)cancelByProducingResumeData:(void (^)(NSData *resumeData))completionHandler
- NSURLSessionTask
/**
* @brief Cancels the task.
*/
- (void)cancel
- NSURLSessionDownloadDelegate
/**
* @brief Tells the delegate that a download task has finished downloading.
*
* @param <session> The session containing the download task that finished.
*
* @param <downloadTask> The download task that finished.
*
* @param <location> A file URL for the temporary file. Because the file is temporary, you must either open the file for reading or move it to a permanent location in your app’s sandbox container directory before returning from this delegate method.
If you choose to open the file for reading, you should do the actual reading in another thread to avoid blocking the delegate queue.
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
/**
* @brief Periodically informs the delegate about the download’s progress.
*
* @param <session> The session containing the download task.
*
* @param <downloadTask> The download task.
*
* @param <bytesWritten> The number of bytes transferred since the last time this delegate method was called.
*
* @param <totalBytesWritten> The total number of bytes transferred so far.
*
* @param <totalBytesExpectedToWriteD> The expected length of the file, as provided by the Content-Length header. If this header was not provided, the value is NSURLSessionTransferSizeUnknown.
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
/**
* @brief Tells the delegate that the download task has resumed downloading.
*
* @param <session> The download task that resumed. See explanation in the discussion.
*
* @param <downloadTask> The download task that finished.
*
* @param <fileOffset> If the file's cache policy or last modified date prevents reuse of the existing content, then this value is zero. Otherwise, this value is an integer representing the number of bytes on disk that do not need to be retrieved again.
*
* @param <expectedTotalBytes> The expected length of the file, as provided by the Content-Length header. If this header was not provided, the value is NSURLSessionTransferSizeUnknown.
*
*/
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
- NSURLSessionTaskDelegate
/**
* @brief Tells the delegate that the task finished transferring data.
*
* @param <session> The session containing the task whose request finished transferring data.
*
* @param <task> The task whose request finished transferring data.
*
* @param <error> If an error occurred, an error object indicating how the transfer failed, otherwise NULL.
*
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
- NSURLSessionDownloadTask
/**
* @brief Cancels a download and calls a callback with resume data for later use.
*
* @param <completionHandler> A completion handler that is called when the download has been successfully canceled.
If the download is resumable, the completion handler is provided with a resumeData object. Your app can later pass this object to a session’s downloadTaskWithResumeData: or downloadTaskWithResumeData:completionHandler: method to create a new task that resumes the download where it left off.
*
*/
- (void)cancelByProducingResumeData:(void (^)(NSData *resumeData))completionHandler
本文介绍了如何利用NSURLSessionDownloadTask在iOS中实现显示下载进度,并在网络中断时进行断点续传。核心API包括NSURLSession、NSURLSessionConfiguration、NSURLSessionTask及其Delegate。文章强调了使用default session和download task,提供了相关代码示例,包括网络状态检测、取消任务以及计算下载进度。同时,还提供了恢复下载任务的步骤。
419

被折叠的 条评论
为什么被折叠?



