下载的一些用法
①对于小文件,可以直接下载,无需断点下载等处理。
1 -(void)clickDownBtn{ 2 NSURL *url = [NSURL URLWithString:@"https://picjumbo.imgix.net/HNCK8461.jpg?q=40&w=1650&sharp=30"]; 3 if (self.imgView.image == nil) { 4 [self downLoad:url]; 5 } 6 } 7 8 //下载过程 9 -(void)downLoad:(NSURL *)url{10 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{11 NSData *imgData = [NSData dataWithContentsOfURL:url];12 UIImage *img = [UIImage imageWithData:imgData];13 if (imgData != nil) {14 dispatch_sync(dispatch_get_main_queue(), ^{15 self.imgView.image = img;16 });17 }18 });19 }
需要注意的是,多线程中,数据处理是在子线程,UI更新是在主线程。若处理数据时没有在子线程中进行,那么会发生线程阻塞、界面卡死的情况。如下载图片时,
-(void)downLoad:(NSURL *)url{ //数据处理(NSData)是在主线程中进行,所以会卡死 //况且这种写法是没有意义的,本来就在主线程,就无需再跳往主线程更新UI NSData *imgData = [NSData dataWithContentsOfURL:url]; UIImage *img = [UIImage imageWithData:imgData]; if (imgData != nil) { dispatch_sync(dispatch_get_main_queue(), ^{ self.imgView.image = img; }); }}
正确的方法是,先开辟子线程,然后在主线程更新UI。
开辟子线程
-(void)downLoad:(NSURL *)url{ //开辟子线程 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //操作 });}
async是异步,sync是同步
②而对于大文件,则可以使用NSURLSession实现暂停、断点下载等操作。如下:
监听下载进度,需要实现代理NSURLSessionDownloadDelegate,在这代理中常用的有3个方法/** 下载完毕会调用,location为文件临时地址 */-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{ }
/** * 每次写入沙盒完毕后调用,在这里可以设置进度条数据等操作 * totalBytesWritten/totalBytesExpectedToWrite,这两个用来监视下载进度 * * @param bytesWritten 这次写入的大小 * @param totalBytesWritten 已经写入沙盒的大小 * @param totalBytesExpectedToWrite 文件总大小 */-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWrittentotalBytesWritten:(int64_t)totalBytesWrittentotalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{ }
/** 恢复下载时使用 */-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes{ }
示例代码如下,在.m中
/** 展示进度的label */@property(nonatomic,strong) UILabel *progressLabel;/** 进度动画 */@property(nonatomic,strong) UIProgressView *progressView;/** 下载按钮 */@property(nonatomic,strong) UIButton *downBtn;/** 下载任务 */@property(nonatomic,strong) NSURLSessionDownloadTask *downLoadTask;/** 缓存数据 */@property(nonatomic,strong) NSData *tempData;/** session */@property(nonatomic,strong) NSURLSession *session;
session的懒加载为
-(NSURLSession *)session{ if (!_session) { NSURLSessionConfiguration *cfg = [NSURLSessionConfiguration defaultSessionConfiguration]; _session = [NSURLSession sessionWithConfiguration:cfg delegate:self delegateQueue:[NSOperationQueue mainQueue]]; } return _session;}
按钮点击事件如下:
//点击下载按钮-(void)clickDownBtn{ self.downBtn.selected = !self.downBtn.selected; if (nil == self.downLoadTask) { if (self.tempData) { [self goOnDownload]; //继续下载 } else { //从0开始下载 [self startDownload]; } } else { [self pauseDownload]; }}/** 开始下载 */-(void)startDownload{ NSURL *url = [NSURL URLWithString:@"http://11.gxdx2.crsky.com/201501/apkok-v3.0.zip"]; //创建任务 self.downLoadTask = [self.session downloadTaskWithURL:url]; //开始任务 [self.downLoadTask resume];}/** 暂停下载 */-(void)pauseDownload{ //防止循环引用 __weak typeof(self) weakSelf = self; [self.downLoadTask cancelByProducingResumeData:^(NSData *resumeData) { //resumeData包含了继续下载的开始位置和url weakSelf.tempData = resumeData; weakSelf.downLoadTask = nil; }];}/** 恢复下载 */-(void)goOnDownload{ //传入上次暂停下载返回的数据,即可恢复下载 self.downLoadTask = [self.session downloadTaskWithResumeData:self.tempData]; [self.downLoadTask resume]; self.tempData = nil;}
实现2个代理方法,第3个暂时用不到
/** 下载完毕会调用,location为文件临时地址 */-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location{ //选择下载文件路径,文件只能下载到该程序的目录中 //下边为下载到Documents文件夹,若将NSDocumentDirectory换为NSCachesDirectory,则会下载到Cache文件夹 NSString *caches = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; //response.suggestedFilename建议使用的文件名,一般跟服务器端的名称一致 NSString *file = [caches stringByAppendingPathComponent:downloadTask.response.suggestedFilename]; //将临时文件复制或剪切到caches文件夹 NSFileManager *fileManager = [NSFileManager defaultManager]; //AtPath : 剪切前的文件路径 //toPath : 剪切后的文件路径 //因为文件都是下载到临时文件夹,下载完成后会删除,所以必须移位置 [fileManager moveItemAtPath:location.path toPath:file error:nil]; // 提示下载完成 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"下载完成" message:downloadTask.response.suggestedFilename delegate:self cancelButtonTitle:nil otherButtonTitles: nil]; [alert show]; [self performSelector:@selector(dismissAlert:) withObject:alert afterDelay:2]; //将下载按钮置灰 self.downBtn.selected = NO; self.downBtn.enabled = NO;}/** * 每次写入沙盒完毕后调用,在这里可以设置进度条数据等操作 * totalBytesWritten/totalBytesExpectedToWrite,这两个用来监视下载进度 * * @param bytesWritten 这次写入的大小 * @param totalBytesWritten 已经写入沙盒的大小 * @param totalBytesExpectedToWrite 文件总大小 */-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWrittentotalBytesWritten:(int64_t)totalBytesWrittentotalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{ //注意该处的progress,等号右边必须强制转换为double型,进度条才有动画效果,否则只有在下载完成后才有 self.progressView.progress = (float)totalBytesWritten/totalBytesExpectedToWrite; //设置成百分比形式 NSString *percent = [NSString stringWithFormat:@"下载进度 %0.0f%%", (double)totalBytesWritten/totalBytesExpectedToWrite*100]; self.progressLabel.text = percent;}
其中一些点要注意下:
一、UIProgressView的progress为float型,所以设置时也要为float型,否则不显示动画效果self.progressView.progress = (float)totalBytesWritten/totalBytesExpectedToWrite;
效果而下:
没有强制转换
self.progressView.progress = totalBytesWritten/totalBytesExpectedToWrite;
效果而下:
二、
NSSearchPathForDirectoriesInDomains可以用来获取app的私有文件路径,即沙盒中的文件夹列表,获取的为数组形式。//获取Document文件夹NSString *document = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];//获取Cache文件夹NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];