iOS进阶_下载管理器(封装下载用工具类)
程序员文章站
2022-05-19 15:54:08
...
同步方法的应用场景:
1.抓取网络数据,如果开启多条线程异步抓取,很容易被封IP
2.加载本地文件,可以直接使用同步方法,比较简单
3. 加载要下载文件的头部信息,HEAD方法
-(void)demo1{
NSURL * url = [NSURL URLWithString:@"http://127.0.0.1/abc.json"];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
/**
同步方法是一个阻塞的!
参数:
1.reques 请求
2.Response *__autoreleasing * 是服务器返回的响应的地址!
3.错误的地址!
*/
NSURLResponse * response = nil;
NSData * data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];
//反序列化
id result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSLog(@"%@ %@",result,response);
}
拓展: __autoreleasing &response
(**表示) 指针的指针
在 C/OC/C++ 中,指针的指针通常使用来在一个方法中返回多个数值!!
今后看到参数是 ** 就是一个指向对象的指针!!!
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSString * name = nil;
int age = 10;
int userID = [self userIDWithAge:&age title:&name];
NSLog(@"userID:%d Age:%d name:%@",userID,age,name);
}
-(int)userIDWithAge:(int *)age title:(NSString **)title {
*title = [NSString stringWithFormat:@"wt"];
*age = 99;
return 1;
}
nil和NULL的区别:
nil :是地址指向NULL的空对象,在OC/C++中,给nil对象发送消息,不起任何作用
NULL:是空地址,本身就是0,就是一个整数,不能给NULL发送消息
什么是安全释放!!
[objc release]; 释放之后,不会修改对象的指针地址.
后续如果继续给objc 发送消息,就会出现野指针错误!!
objc = nil;//对象地址已经指向了 NULL(0),这个时候再发消息,都不会报错!!
下载管理器的实现思路
这才是正式的文章,上面的是餐前甜点。。。
这次我们自己来封装一个做下载用的工具类
目的 -> 下载
1.先实现一个简单的下载功能
2.对外提供接口
NSURLSession下载
1.跟踪进度
2.断点续传,问题:这个resumeData丢失,再次下载的时候,无法续传!!
考虑解决方案:
- 将文件保存在固定的位置
- 再次下载文件前,先检查固定位置是否存在文件
- 如果有,直接续传!!!
封装下载工具类
WTDownloader.h
#import <Foundation/Foundation.h>
@interface WTDownloader : NSObject
/**
* 下载指定url的文件
*
* @param url 要下载的url
*/
-(void)downloadWithURL:(NSURL *)url;
@end
WTDownloader.m
#import "WTDownloader.h"
#define kTimeOut 20.0
@interface WTDownloader ()<NSURLConnectionDataDelegate>
/** 文件输出流 */
@property(nonatomic,strong)NSOutputStream * fileStream;
/** 网络文件总大小 */
@property(assign,nonatomic)long long expectedContentLength;
/** 本地文件总大小 */
@property(assign,nonatomic)long long currentLength;
/** 文件路径 */
@property(copy,nonatomic)NSString * filePath;
/** 下载文件的URL */
@property(nonatomic,strong)NSURL * downloadURL;
/** 下载的Runloop */
@property(assign,nonatomic)CFRunLoopRef downloadRunloop;
//--------------BLOCK属性---------------
@property(copy,nonatomic)void(^progressBlock)(float);
@property(copy,nonatomic)void(^completionBlock)(NSString *);
@property(copy,nonatomic)void(^failedBlock)(NSString *);
@end
/**
NSURLSession下载
1.跟踪进度
2.断点续传,问题:这个resumeData丢失,再次下载的时候,无法续传!!
考虑解决方案:
- 将文件保存在固定的位置
- 再次下载文件前,先检查固定位置是否存在文件
- 如果有,直接续传!!!
*/
@implementation WTDownloader
/**
很多三方框架有一个共同特点(SDWebImage/AFN/ASI)
进度的回调,是在异步线程回调的
-- 因为进度回调会调用多次,如果在主线程,会影响UI交互!!
完成之后的回调,在主线程
-- 通常调用方不需要关心线程间的通讯,一旦完成直接更新UI更方便
*/
-(void)downloadWithURL:(NSURL *)url Progress:(void (^)(float))progress completion:(void (^)(NSString *))completion failed:(void (^)(NSString *))failed
{
//0.保存属性
self.downloadURL = url;
self.progressBlock = progress;
self.completionBlock = completion;
self.failedBlock = failed;
//1.检查服务器上的文件大小!
[self serverFileInfoWithURL:url];
NSLog(@"%lld %@",self.expectedContentLength,self.filePath);
//2.检查本地文件大小!
if(![self checkLocalFileInfo]){
NSLog(@"文件已经下载完毕了!!");
return;
};
//3.如果需要,从服务器开始下载!
NSLog(@"从我们的%lld下载文件",self.currentLength);
[self downloadFile];
}
#pragma mark - <下载文件>
//从 self.currentLength 开始下载文件
-(void)downloadFile{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//1.建立请求
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:self.downloadURL cachePolicy:1 timeoutInterval:kTimeOut];
//设置下载的字节范围 从 self.currentLength 开始之后所有的字节
NSString * rangeStr = [NSString stringWithFormat:@"bytes=%lld-",self.currentLength];
//设置请求头字段
[request setValue:rangeStr forHTTPHeaderField:@"Range"];
//2.开始网络连接
NSURLConnection * conn = [NSURLConnection connectionWithRequest:request delegate:self];
//3.启动完了连接
[conn start];
//4.利用运行循环实现多线程不被回收
self.downloadRunloop = CFRunLoopGetCurrent();
CFRunLoopRun();
});
}
#pragma mark - <私有方法>
/**
* 检查本地文件信息 --> 判断是否需要下载
*
* @return YES 需要下载, NO 不需要下载
*/
-(BOOL)checkLocalFileInfo{
long long fileSize = 0;
//1.文件是否存在
if([[NSFileManager defaultManager] fileExistsAtPath:self.filePath]){
//2.获取文件大小
NSDictionary * attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:self.filePath error:NULL];
//fileSize = [attributes[NSFileSize] longLongValue];
//利用分类方法获取文件大小
fileSize = [attributes fileSize];
}
/*
如果大小小于服务器的大小,从本地文件的长度开始下载!!!(续传)
如果大小等于服务器的大小,认为文件已经下载完毕
如果大小大于服务器的大小,直接干掉,重新下载
*/
//大于服务器的文件
if (fileSize > self.expectedContentLength) {
//删除这个文件
[[NSFileManager defaultManager] removeItemAtPath:self.filePath error:NULL];
fileSize = 0;
}
//是否文件和服务器的文件大小一样
self.currentLength = fileSize;
if (fileSize == self.expectedContentLength) {
if (self.completionBlock) {
self.completionBlock(self.filePath);
}
return NO;
}
return YES;
}
//检查服务器文件大小
-(void)serverFileInfoWithURL:(NSURL *)url{
//1.请求
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url cachePolicy:1 timeoutInterval:kTimeOut];
request.HTTPMethod = @"HEAD";
//2.建立网络连接
NSURLResponse * response = nil;
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];
//3.记录服务器的文件信息
//3.1 文件长度
self.expectedContentLength = response.expectedContentLength;
//3.2 建议保存的文件名,将在的文件保存在tmp ,系统会自动回收
self.filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:response.suggestedFilename];
return;
}
#pragma mark - <NSURLConnectionDataDelegate>
//1.接收到服务器的响应
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
//打开输出流
self.fileStream = [[NSOutputStream alloc] initToFileAtPath:self.filePath append:YES];
[self.fileStream open];
}
//2.接收到数据,用输出流拼接,计算下载进度
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
//追加数据
[self.fileStream write:data.bytes maxLength:data.length];
//记录文件的长度
self.currentLength += data.length;
float progress = (float)self.currentLength / self.expectedContentLength;
//判断block是否存在
if (self.progressBlock) {
self.progressBlock(progress);
}
}
//3.所有下载完毕
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//关闭流
[self.fileStream close];
//停止运行循环
CFRunLoopStop(self.downloadRunloop);
//判断BLock是否存在
if (self.completionBlock) {
//主线程回调
dispatch_async(dispatch_get_main_queue(), ^{self.completionBlock(self.filePath); });
}
}
//4.出错
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
//关闭流
[self.fileStream close];
//停止运行循环
CFRunLoopStop(self.downloadRunloop);
//判断BLock是否存在
if (self.failedBlock) {
self.failedBlock(error.localizedDescription);
}
NSLog(@"%@",error.localizedDescription);
@end
工具类调用
#import "ViewController.h"
#import "WTDownloader.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
WTDownloader * downloader = [[WTDownloader alloc]init];
NSURL * url = [NSURL URLWithString:@"http://127.0.0.1/abc.wmv"];
[downloader downloadWithURL:url Progress:^(float progress) {
NSLog(@"--->%f %@",progress,[NSThread currentThread]);
} completion:^(NSString *filePath) {
//下载成功了
NSLog(@"下载完成了 %@ %@",filePath,[NSThread currentThread]);
} failed:^(NSString *errorMsg) {
NSLog(@"下载失败了:%@",errorMsg);
}];
}
@end
上一篇: github项目管理
下一篇: linux 搭建svn服务器