网络编程归纳总结七阶段
断点续传
文件下载的时候不能像上一个练习中一样,文件存在不能直接删除文件。应该做一些决策
判断本地文件,如果本地文件存在要判断文件的大小
如果没有本地文件,下载
如果本地文件存在,发送Head请求获取服务器文件大小
本地文件大小==服务器文件大小,不下载
本地文件大小< 服务器文件大小,从之前的位置开始下载
本地文件大小> 服务器文件大小,删除本地文件,从新下载
检查服务器文件
//获取服务器上文件的信息(文件名和文件大小)
- (void)checkServerInfo:(NSURL *)url{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"head";
NSURLResponse *response = nil;
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];
//通过属性记录文件的大小,拼接要保持的文件的路径
self.expectedContentLength = response.expectedContentLength;
self.targetPath = [NSTemporaryDirectory() stringByAppendingPathComponent:response.suggestedFilename];
}
检查本地文件
//如果没有文件 返回0 从头下载
//如果文件的大小>服务器文件大小 返回0 删除文件 从头下载
//如果文件的大小=服务器文件大小 返回文件大小 如果大小下等,不用再次下载
//如果文件的大小<服务器文件大小 返回文件大小
- (long long)checkLocalInfo{
NSFileManager *manager = [NSFileManager defaultManager];
long long fileSize = 0;
//检查是否有文件
if ([manager fileExistsAtPath:self.targetPath]) {
//获取文件大小
NSDictionary *attrsDic = [manager attributesOfItemAtPath:self.targetPath error:NULL];
fileSize = attrsDic.fileSize;
//获取文件大小
// NSLog(@"%lld",[attrsDic[NSFileSize] longLongValue]);
// NSLog(@"%lld",attrsDic.fileSize);
}
if (fileSize > self.expectedContentLength) {
//如果本地的文件大小。比服务器的大。删除
[manager removeItemAtPath:self.targetPath error:NULL];
fileSize = 0;
}
return fileSize;
}
下载
如果本地文件和服务器文件大小相等,不下载
//检查本地文件
long long fileSize = [self checkLocalInfo];
NSLog(@"%lld",fileSize);
if (fileSize == self.expectedContentLength) {
NSLog(@"已经下载");
return;
}
从指定偏移处开始下载
//从指定偏移出下载文件
- (void)downloadFile:(NSURL *)url offset:(long long)offset{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:1 timeoutInterval:15];
self.currentFileSize = offset;
//range的取值
//bytes=x-y 从x字节开始下载,下载到y字节
//bytes=x- 从x字节开始下载,直到最后
//bytes=-x 从0字节开始下载,下载到x字节
//range: bytes=x-y
NSString *rangeStr = [NSString stringWithFormat:@"bytes=%lld-",offset];
[request setValue:rangeStr forHTTPHeaderField:@"range"];
//设置connection的代理
NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
//开始
[conn start];
}
异步下载
默认代理的方法都在主线程上执行,下载会卡死
异步下载
NSURLConnection的代理方法,想在子线程上执行的话必须开启消息循环
把下载方法中的所有代码都放在异步队列中执行
- (void)download:(NSString *)urlStr
[[NSOperationQueue new] addOperationWithBlock:^{}]
在指定位置处开启消息循环,消息循环的模式必须是default模式
NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
//启动消息循环
[[NSRunLoop currentRunLoop] run];
下载完成后的回调
下载完成或出错之后要在主界面做提示,现在所有的下载操作都封装在Downloader这个类中学习SDWebImage中的做法,给下载操作传入需要的block,当下载完成或出错的时候调用
修改Downloader头文件中的下载方法,增加需要的block,进度、完成、出错的block
修改Downloader.m中的实现方法,因为具体的进度、完成、出错都是在URLConnection的代理方法实现,所以传入block后需要定义属性接收
对应的位置调用回调方法
//URLConnection的下载方法中调用进度的回调,在当前子线程中执行
if (self.progressBlock) {
self.progressBlock(progress);
}
URLConnection的下载完成的方法中调用完成的回调,下载完成会回归主线程调用,在主线程上更新界面
if (self.successBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
self.successBlock(self.targetPath);
});
}
URLConnection的下载出错的方法中调用出错的回调,在哪个线程执行由调用者决定
if (self.errorBlock) {
self.errorBlock(error);
}
如果文件已经在,也要调用完成的回调
controller中调用
下载进度提示
界面上放置一个自定义按钮,设置大小
创建按钮的自定义类(按钮必须是custom的)
定义一个progress的属性,把进度传过来
每当给progress属性赋值的时候调用setNeedsDisplay重绘
- (void)setProgress:(CGFloat)progress{
_progress = progress;
[self setTitle:[NSString stringWithFormat:@"%0.2f%%",progress*100] forState:UIControlStateNormal];
[self setNeedsDisplay];
}
画进度
重写drawRect方法,根据progress画圆
- (void)drawRect:(CGRect)rect {
UIBezierPath *path = [UIBezierPath bezierPath];
CGSize size = self.frame.size;
//原点
CGPoint center = CGPointMake(size.width/2, size.height/2);
//半径
CGFloat radius = (MIN(size.width, size.height) - 5)/2;
//起始位置 从最上面开始画
CGFloat startAngle = -M_PI_2;
//结束位置
CGFloat endAngle = 2*M_PI*self.progress+startAngle;
[path addArcWithCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
path.lineWidth = 5;
path.lineCapStyle = kCGLineCapRound;
[[UIColor orangeColor] setStroke];
[path stroke];
}
暂停下载
//暂停下载,暂停下载就是调用connection的cancel方法
- (void)pause{
[self.conn cancel];
}
下载管理类和缓存池
示例代码:
//创建下载的管理类HMDownloaderManager,单例
+ (instancetype)defaultManager{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
下载,调用下载器的下载方法
定义缓存池,当开始下载,把下载器缓存起来
下载之前先判断缓存池中是否有下载操作
下载完成或失败后,从缓存池移除下载操作—–最终解决重复下载的问题
下载管理类
- (void)download:(NSString *)urlStr progress:(void (^)(float))progress success:(void (^)(NSString *))success error:(void (^)(NSError *))error{
//创建下载器之前。先判断针对该文件是否有下载操作
if ([self.downloadCache objectForKey:urlStr] != nil) {
NSLog(@"正在下载");
return;
}
//1 下载器
HMDownloader *downloader = [[HMDownloader alloc] init];
//2 把下载器添加到缓存池
[self.downloadCache setObject:downloader forKey:urlStr];
//3 下载
[downloader download:urlStr progress:progress success:^(NSString *targetPath) {
[self.downloadCache removeObjectForKey:urlStr];
//下载完成
if (success != nil) {
success(targetPath);
}
} error:^(NSError *e) {
[self.downloadCache removeObjectForKey:urlStr];
//出错
if (error != nil) {
error(e);
}
}];
}
Downloader改成NSOperation
+ (instancetype)downloader:(NSString *)urlStr progress:(void (^)(float))progress success:(void (^)(NSString *))success error:(void (^)(NSError *))error{
HMDownloader *downloader = [[HMDownloader alloc] init];
downloader.progressBlock = progress;
downloader.successBlock = success;
downloader.errorBlock = error;
downloader.urlStr = urlStr;
return downloader;
}
- (void)main{
@autoreleasepool {
NSURL *url = [NSURL URLWithString:str];
//1 检查服务器文件信息
[self checkServerInfo:url];
//2 检查本地文件
long long fileSize = [self checkLocalInfo];
// NSLog(@"%lld",fileSize);
if (fileSize == self.expectedContentLength) {
NSLog(@"已经下载");
if (self.successBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
self.successBlock(self.targetPath);
});
}
return;
}
//3 根据本地文件的长度 从对应偏移位置开始下载
[self downloadFile:url offset:fileSize];
}
}
取消下载操作
- (void)pause:(NSString *)urlStr{
if ([self.downloadCache objectForKey:urlStr] == nil) {
NSLog(@"没有此下载操作");
return;
}
JSDownloader *downloader = [self.downloadCache objectForKey:urlStr];
//暂停 connection
[downloader pause];
//取消操作
[downloader cancel];
//把操作从缓存池中移除
[self.downloadCache removeObjectForKey:urlStr];
}
下载操作中还要取消正在执行的操作
//取消正在执行的操作
if (self.isCancelled) {
return;
}
最近才开始往github上放东西 在公司写的又不能放= = 大家姑且看看吧
github地址: https://github.com/FuThD