前几天面试被人问到后台下载的问题,当时就GG了,之前写Andriod的时候,用service写过,但是iOS没有做过这方面的。回去后查了一些资料,就有了这篇博文。
NSURLSession出来之后,我们可以很容易实现后台下载这种任务,简单使用backgroundSessionConfigurationWithIdentifier:就OK了。
下面讲解如何实现:
首先让我们在ViewController的类拓展里添加如下代码:
@interface ViewController ()<NSURLSessionDelegate,NSURLSessionDownloadDelegate,NSURLSessionTaskDelegate>
@property (nonatomic,strong) NSURLSession *session;
@property (nonatomic,strong) NSURLSessionDownloadTask *task;
@end
我们在里面声明里NSURLSession和NSURLSessionDownTask的属性,还实现了NSURLSessionDelegate,NSURLSessionDownloadDelegate,NSURLSessionTaskDelegate这三个必须实现的代理。
接着我们去实现下载的方法:
- (void)downloadBackground
{
NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"download"];
//系统根据当前性能自动处理后台任务的优先级
config.discretionary = YES;
_session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
//下面两个是不同的文件,一个是一张图片,一个是微博,大家可以选择不同的网址去实验
// http://farm3.staticflickr.com/2846/9823925914_78cd653ac9_b_d.jpg
// http://dlsw.baidu.com/sw-search-sp/soft/3f/12289/Weibo.4.5.3.37575common_wbupdate.1423811415.exe
NSURL *url = [NSURL URLWithString:@"http://dlsw.baidu.com/sw-search-sp/soft/3f/12289/Weibo.4.5.3.37575common_wbupdate.1423811415.exe"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
_task = [_session downloadTaskWithRequest:request];
[_task resume];
}
NSURLSession的固定写法,定义后台下载的NSURLSessionConfiguration,定义session,再定义一个request和task,启动task。
这样就可以了吗?当然不是,我们还需要实现相应的代理方法,再添加下面的方法
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
//下载过程不断回调
NSLog(@"当前进度%f%%",totalBytesWritten * 1.0 / totalBytesExpectedToWrite * 100);
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
//下载结束回调,注意这里得到的文件是一个临时文件,为了保存这个文件,我们需要把它copy到Document目录下
NSLog(@"file--%@,path--%@",downloadTask.description,location);
}
好了,在viewDidLoad函数中调用downloadBackground,现在运行工程,应该可以看到不断打印的进度,在模拟器按一下command+shift+h回到桌面,发现程序依然在下载,并没有停滞,不过最后发现打印出来的path是.tmp文件,像上面注释说的,这里我们得到的是一个临时文件,我们必须将这个文件copy到Document目录下。当然如果你什么都看不到,只看到一条报错信息,请确保你已经在你的plist文件加入以下键值对:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
为什么要这样写?请度娘!!!!
好了,现在让我们实现copy文件的函数
- (void)copyFileAtPath:(NSString *)path
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [paths objectAtIndex:0];
NSLog(@"document--%@",documentDirectory);
//测试图片的网址,改这个
// NSString *toPath = [documentDirectory stringByAppendingPathComponent:@"1.jpg"];
NSString *toPath = [documentDirectory stringByAppendingPathComponent:@"weibo.exe"];
if (![fileManager fileExistsAtPath:toPath]) {
[fileManager copyItemAtPath:path toPath:toPath error:&error];
if (error) {
NSLog(@"copy error--%@",error.description);
}
}
}
上面这个函数首先得到Document文件夹的路径,再拼接出文件路径,最后就是判断文件是否存在,不存在就复制。
请在下载完成的回调函数里调用这个方法,等到下载完成之后,复制控制台打印的document路径,在桌面按command+shift+g,会弹出前往的窗口,粘贴刚刚复制的路径,点击前往,你会看到这样的图片:
做到这里,后台下载就完成了。不过我们可以做得更好一些,让我们给它加上断点续传的功能。先让我们用StoryBoard拉个简单的界面,不熟悉StroyBoard的请自行度娘。界面大概是这样的:
接着在类拓展里添加下面的属性:
@property (nonatomic,strong) NSData *data;
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
记住progressView属性是拉出来的,是拉出来的,是拉出来的。接着再拉出下面三个方法,分别添加如下代码:
- (IBAction)startAction:(UIButton *)sender {
[self downloadBackground];
}
- (IBAction)pauseAction:(UIButton *)sender {
if (!_task) {
return;
}
[_task cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
_data = resumeData;
}];
}
- (IBAction)resumeAction:(UIButton *)sender {
if (!_data) {
return;
}else {
_task = [_session downloadTaskWithResumeData:_data];
[_task resume];
}
}
解释一下上面的代码,首先把downloadBackground方法移到开始按钮的点击事件里,pauseAction主要就是调用cancelByProducingResumeData方法,用data属性记录resumeData,在resumeAction里重新启动task,注意是调用downloadTaskWithResumeData方法。
做到这里还没有完哦,我们的progressView还没有刷新呢。所以在下载过程调用的回调代理里添加代码:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
NSLog(@"当前进度%f%%",totalBytesWritten * 1.0 / totalBytesExpectedToWrite * 100);
//因为这里不是主线程,所以需要回到主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
self.progressView.progress = totalBytesWritten * 1.0 / totalBytesExpectedToWrite;
});
}
现在再运行,你会看到下面的效果:
好了,这就完成了后台下载和断点续传了。代码请点链接,文件夹为后台下载和本地通知