一、开启Mac本地Apache服务器
二、NNSURLConnection文件下载
2.1、方式一:sendAsynchronousRequest + Block
//1、url
NSString *urlStr = @"http://127.0.0.1/下载视频.wmv";
//1.1、url有中文,需要转码
urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString: urlStr];
//2、request
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3、connection
NSLog(@"开始");
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
//将数据写入到磁盘
[data writeToFile:@"/Users/liuyi/Desktop/123/123.wmv" atomically:YES];
NSLog(@"完成!");
}];
问题:
1.没有下载进度、会影响用户体验
解决方法:
--通过代理方式来解决
1.进度跟进
--在响应头方法中获取文件的总大小
--每次接收数据的,计算数据的比例
2.内存偏高,会有最大的峰值!
保存文件的思路
- 保存完成写入磁盘
测试结果:和异步方法执行的效果一样,仍然存在内存问题。
推测:苹果的异步方法的实现思路,就我们刚刚写的代码
- 边下载边写
1.NSFileHandle 彻底解决了内存峰值的问题。
2.NSOutputStream 输出流
新的问题:
默认的connection 是在主线程工作,指定了代理的工作的队列之后,整个下载还是在主线程 。UI事件能够卡住下载
2.2、方式二:避开Block,显示加载进度 -- Delegate
进度跟进
--在响应头方法中获取文件的总大小
--每次接收数据的,计算数据的比例
//1.url
NSString *urlStr = @"http://127.0.0.1/下载视频.wmv";
//1.1.url有中文,需要转码
urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString: urlStr];
//2.request
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.connection + delegate
NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
//启动连接
[conn start];
注意:
NSURLConnectionDownloadDelegate
:千万不要用!专门针对杂志下载提供的接口
如果NSURLConnectionDownloadDelegate
下载,能够监听到下载进度。没有办法找到下载文件
Newsstand Kit's
专门用来做杂志的Kit
#pragma mark -- NSURLConnectionDeleagate
//1.接收服务器的响应 ---状态行和响应头--做一些准备工作
// expectedContentLength 文件的总大小
// suggestedFilename 建议保存的名字
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(@"%@",response);
//记录文件总大小
self.expectedContentLength = response.expectedContentLength;
self.currentlength = 0;
//生成目标文件路径
self.tartgetFilePath = [@"/Users/linxiang/Desktop/123" stringByAppendingPathComponent:response.suggestedFilename];
}
// 懒加载 - 初始化
-(NSMutableData *)fileData
{
if (!_fileData) {
_fileData = [[NSMutableData alloc]init];
}
return _fileData;
}
//2.接收服务器的数据 -- 此代理方法会被执行多次! 因此我们会拿到多个data
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
//NSLog(@"接收数据的长度 %tu",data.length);
self.currentlength += data.length;
//计算百分比
//progress = (float)long long / long long
float progress = (float)self.currentlength / self.expectedContentLength;
NSLog(@"%f",progress);
//拼接data
[self.fileData appendData:data];
}
//3.所有数据加载完毕--所有数据加载完毕,会一个通知!
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"完毕!");
//数据吸写入磁盘
[self.fileData writeToFile:self.tartgetFilePath atomically:YES];
}
//4.下载失败或者错误
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(@"异常!");
}
问题:
1.内存偏高,会有最大的峰值!
保存文件的思路
- 保存完成写入磁盘
测试结果:和异步方法执行的效果一样,仍然存在内存问题。
推测:苹果的异步方法的实现思路,就我们刚刚写的代码
- 边下载边写
1.NSFileHandle 彻底解决了内存峰值的问题。
2.3、方式三:为了解决内存峰值 -- 边下载边写入 NSFileHandle
NSFileManager
: 主要功能,创建目录、检查目录是否存在,遍历目录、删除文件、拷贝文件、针对文件的操作;
NSFileHandle
: 文件“句柄”,对文件的操作! 主要功能:就同一个文件进行二进制读写;
-(void)writeFileData:(NSData *)data
{
/*
如何判断文件是否下载完成!
1.判断进度?判断完成通知?
2.判断时间、判断大小?
3.MD5
最合理的方案:
1.服务器会对你的下载文件 计算好一个MD5 将此MD5 传给客户端;
2.开始下载文件。。。。。
3.下载完成时,对下载的文件做一次MD5
4.比较服务器返回的MD5 和你 自己计算的MD5 比较 == 下载完成!
*/
NSFileHandle *fp = [NSFileHandle fileHandleForWritingAtPath:self.tartgetFilePath];
//如果文件不存在,直接将数据写入磁盘
if (fp == nil) {
[data writeToFile:self.tartgetFilePath atomically:YES];
}else
{
//如果存在,将data追加到现在文件的末尾
[fp seekToEndOfFile];
//写入文件
[fp writeData:data];
//关闭文件
[fp closeFile];
}
}
2.4:方式四:为了解决内存峰值 -- 边下载边写入 NSOutputStream
//1、创建输出流
self.fileStream = [[NSOutputStream alloc]initToFileAtPath:self.tartgetFilePath append:YES];
[self.fileStream open];
//2、判断是否有空间可写
if ([self.fileStream hasSpaceAvailable]) {
//2.2、写入数据
[self.fileStream write:data.bytes maxLength:data.length];
}
//3、关闭文件流
[self.fileStream close];
新的问题:
默认的connection 是在主线程工作,指定了代理的工作的队列之后,整个下载还是在主线程 。UI事件能够卡住下载
2.5、方式五:为了解决 " connection下载在主线程,从而卡住页面,或者UI卡住下载 " 的情况 -- RunLoop
1、将
NSConnection
请求放入子线程
中,这样就行了吗??
------答案是否定的:由于子线程的RunLoop
默认不开启,导致子线程执行完成就关闭了,根本没有时间进行下载的操作;
2、所以需要我们手动开启子线程的RunLoop
;
3、下载完成,需要我们关闭子线程的RunLoop
,让子线程退出;
//将网络操作放在异步线程,异步的运行循环默认不启动,没有办法监听接下来的网络事件
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//1.url
NSString *urlStr = @"http://127.0.0.1/下载视频.wmv";
urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString: urlStr];
//2.request
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.connection
NSLog(@"开始");
/*
For the connection to work correctly, the calling thread’s run loop must be operating in the default run loop mode.
为了保证链接正常工作,调用线程的runloop 必须运行在默认的运行循环模式下
*/
NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
//设置代理工作时操作
[conn setDelegateQueue:[[NSOperationQueue alloc]init]];
//启动连接
[conn start];
//5.启动运行循环 - 让子线程不死
/*
CoreFoundation 框架 CFRunLoop
CFRunloopStop() 停止指定的runloop
CFRunloopGetCurrent() 获取当前的Runloop
CFRunloopRun() 直接启动当前的运行循环
*/
//1、拿到当前的运行循环
self.downloadRunloop = CFRunLoopGetCurrent();
//2.启动当前的运行循环
CFRunLoopRun();
NSLog(@"来了");
});
下载完成后,手动停止下载线程所在的RunLoop
//3.所有数据加载完毕--所有数据加载完毕,会一个通知!
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"完毕!%@",[NSThread currentThread]);
//停止下载线程所在的runloop
CFRunLoopStop(self.downloadRunloop);
}