NSURLConnection和Runloop
- 两种为NSURLConnection设置代理方式的区别
//第一种设置方式:
//通过该方法设置代理,会自动的发送请求
// [[NSURLConnection alloc]initWithRequest:request delegate:self];
//第二种设置方式:
//设置代理,startImmediately为NO的时候,该方法不会自动发送请求
NSURLConnection *connect = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO];
//手动通过代码的方式来发送请求
//注意该方法内部会自动的把connect添加到当前线程的RunLoop中在默认模式下执行
[connect start];
- 如何控制代理方法在哪个线程调用
//说明:默认情况下,代理方法会在主线程中进行调用(为了方便开发者拿到数据后处理一些刷新UI的操作不需要考虑到线程间通信)
//设置代理方法的执行队列
[connect setDelegateQueue:[[NSOperationQueue alloc]init]];
- 开子线程发送网络请求的注意点,适用于自动发送网络请求模式
//在子线程中发送网络请求-调用startf方法发送
-(void)createNewThreadSendConnect1
{
//1.创建一个非主队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//2.封装操作,并把任务添加到队列中执行
[queue addOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
//2-1.确定请求路径
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=dd&pwd=ww&type=JSON"];
//2-2.创建请求对象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//2-3.使用NSURLConnection设置代理,发送网络请求
NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:YES];
//2-4.设置代理方法在哪个队列中执行,如果是非主队列,那么代理方法将再子线程中执行
[connection setDelegateQueue:[[NSOperationQueue alloc]init]];
//2-5.发送网络请求
//注意:start方法内部会把当前的connect对象作为一个source添加到当前线程对应的runloop中
//区别在于,如果调用start方法开发送网络请求,那么再添加source的过程中,如果当前runloop不存在
//那么该方法内部会自动创建一个当前线程对应的runloop,并启动。
[connection start];
}];
}
//在子线程中发送网络请求-自动发送网络请求
-(void)createNewThreadSendConnect2
{
NSLog(@"-----");
//1.创建一个非主队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//2.封装操作,并把任务添加到队列中执行
[queue addOperationWithBlock:^{
//2-1.确定请求路径
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=dd&pwd=ww&type=JSON"];
//2-2.创建请求对象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//2-3.使用NSURLConnection设置代理,发送网络请求
//注意:该方法内部虽然会把connection添加到runloop,但是如果当前的runloop不存在,那么不会主动创建。
NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
//2-4.设置代理方法在哪个队列中执行,如果是非主队列,那么代理方法将再子线程中执行
[connection setDelegateQueue:[[NSOperationQueue alloc]init]];
//2-5 创建当前线程对应的runloop,并开启
[[NSRunLoop currentRunLoop]run];
}];
}
实现断点续传代码
- 实现思路
- 在下载文件的时候不再是整块的从头开始下载,而是看当前文件已经下载到哪个地方,然后从该地方接着往后面下载。可以通过在请求对象中设置请求头实现。
- 解决方案(设置请求头)
//2.创建请求对象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//2.1 设置下载文件的某一部分
// 只要设置HTTP请求头的Range属性, 就可以实现从指定位置开始下载
/*
表示头500个字节:Range: bytes=0-499
表示第二个500字节:Range: bytes=500-999
表示最后500个字节:Range: bytes=-500
表示500字节以后的范围:Range: bytes=500-
*/
NSString *range = [NSString stringWithFormat:@"bytes=%zd-",self.currentLength];
[request setValue:range forHTTPHeaderField:@"Range"];
(3)注意点(下载进度并判断是否需要重新创建文件)
//获得当前要下载文件的总大小(通过响应头得到)
NSHTTPURLResponse *res = (NSHTTPURLResponse *)response;
//注意点:res.expectedContentLength获得是本次请求要下载的文件的大小(并非是完整的文件的大小)
//因此:文件的总大小 == 本次要下载的文件大小+已经下载的文件的大小
self.totalLength = res.expectedContentLength + self.currentLength;
NSLog(@"----------------------------%zd",self.totalLength);
//0 判断当前是否已经下载过,如果当前文件已经存在,那么直接返回
if (self.currentLength >0) {
return;
}
- 完整代码
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
@property (nonatomic, assign) NSInteger totalSize;
@property (nonatomic, assign) NSInteger currentSize;
/** 文件句柄*/
@property (nonatomic, strong)NSFileHandle *handle;
/** 沙盒路径 */
@property (nonatomic, strong) NSString *fullPath;
/** 连接对象 */
@property (nonatomic, strong) NSURLConnection *connect;
@end
@implementation ViewController
- (IBAction)startBtnClick:(id)sender {
[self download];
}
- (IBAction)cancelBtnClick:(id)sender {
[self.connect cancel];
}
- (IBAction)goOnBtnClick:(id)sender {
[self download];
}
//内存飙升
-(void)download
{
//1.url
// NSURL *url = [NSURL URLWithString:@"http://imgsrc.baidu.com/forum/w%3D580/sign=54a8cc6f728b4710ce2ffdc4f3cec3b2/d143ad4bd11373f06c0b5bd1a40f4bfbfbed0443.jpg"];
NSURL *url = [NSURL URLWithString:@"http://www.33lc.com/article/UploadPic/2012-10/2012102514201759594.jpg"];
//2.创建请求对象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//设置请求头信息,告诉服务器值请求一部分数据range
/*
bytes=0-100
bytes=-100
bytes=0- 请求100之后的所有数据
*/
NSString *range = [NSString stringWithFormat:@"bytes=%zd-",self.currentSize];
[request setValue:range forHTTPHeaderField:@"Range"];
NSLog(@"+++++++%@",range);
//3.发送请求
NSURLConnection *connect = [[NSURLConnection alloc]initWithRequest:request delegate:self];
self.connect = connect;
}
#pragma mark ----------------------
#pragma mark NSURLConnectionDataDelegate
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(@"didReceiveResponse");
//1.得到文件的总大小(本次请求的文件数据的总大小 != 文件的总大小)
// self.totalSize = response.expectedContentLength + self.currentSize;
if (self.currentSize >0) {
return;
}
self.totalSize = response.expectedContentLength;
//2.写数据到沙盒中
self.fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]stringByAppendingPathComponent:@"123.jpg"];
NSLog(@"%@",self.fullPath);
//3.创建一个空的文件
[[NSFileManager defaultManager] createFileAtPath:self.fullPath contents:nil attributes:nil];
//NSDictionary *dict = [[NSFileManager defaultManager]attributesOfItemAtPath:self.fullPath error:nil];
//4.创建文件句柄(指针)
self.handle = [NSFileHandle fileHandleForWritingAtPath:self.fullPath];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
//1.移动文件句柄到数据的末尾
[self.handle seekToEndOfFile];
//2.写数据
[self.handle writeData:data];
//3.获得进度
self.currentSize += data.length;
//进度=已经下载/文件的总大小
NSLog(@"%f",1.0 * self.currentSize/self.totalSize);
self.progressView.progress = 1.0 * self.currentSize/self.totalSize;
//NSLog(@"%@",self.fullPath);
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//1.关闭文件句柄
[self.handle closeFile];
self.handle = nil;
NSLog(@"connectionDidFinishLoading");
NSLog(@"%@",self.fullPath);
}
@end
输出流
- 使用输出流也可以实现和NSFileHandle相同的功能
- 如何使用
//1.创建一个数据输出流
/*
第一个参数:二进制的流数据要写入到哪里
第二个参数:采用什么样的方式写入流数据,如果YES则表示追加,如果是NO则表示覆盖
*/
NSOutputStream *stream = [NSOutputStream outputStreamToFileAtPath:fullPath append:YES];
//只要调用了该方法就会往文件中写数据
//如果文件不存在,那么会自动的创建一个
[stream open];
self.stream = stream;
//2.当接收到数据的时候写数据
//使用输出流写数据
/*
第一个参数:要写入的二进制数据
第二个参数:要写入的数据的大小
*/
[self.stream write:data.bytes maxLength:data.length];
//3.当文件下载完毕的时候关闭输出流
//关闭输出流
[self.stream close];
self.stream = nil;
使用多线程下载文件思路
01 开启多条线程,每条线程都只下载文件的一部分(通过设置请求头中的Range来实现)
02 创建一个和需要下载文件大小一致的文件,判断当前是那个线程,根据当前的线程来判断下载的数据应该写入到文件中的哪个位置。(假设开5条线程来下载10M的文件,那么线程1下载0-2M,线程2下载2-4M一次类推,当接收到服务器返回的数据之后应该先判断当前线程是哪个线程,假如当前线程是线程2,那么在写数据的时候就从文件的2M位置开始写入)
03 代码相关:使用NSFileHandle这个类的seekToFileOfSet方法,来向文件中特定的位置写入数据。
04 技术相关
a.每个线程通过设置请求头下载文件中的某一个部分
b.通过NSFileHandle向文件中的指定位置写数据
小文件下载方式
第一种方式(NSData)
//使用NSDta直接加载网络上的url资源(不考虑线程)
-(void)dataDownload
{
//1.确定资源路径
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_01.png"];
//2.根据URL加载对应的资源
NSData *data = [NSData dataWithContentsOfURL:url];
//3.转换并显示数据
UIImage *image = [UIImage imageWithData:data];
self.imageView.image = image;
}
第二种方式(NSURLConnection-sendAsync)
//使用NSURLConnection发送异步请求下载文件资源
-(void)connectDownload
{
//1.确定请求路径
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_01.png"];
//2.创建请求对象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.使用NSURLConnection发送一个异步请求
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
//4.拿到并处理数据
UIImage *image = [UIImage imageWithData:data];
self.imageView.image = image;
}];
}
第三种方式(NSURLConnection-delegate)
//使用NSURLConnection设置代理发送异步请求的方式下载文件
-(void)connectionDelegateDownload
{
//1.确定请求路径
NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
//2.创建请求对象
NSURLRequest *request = [NSURLRequest requestWithURL:url];
//3.使用NSURLConnection设置代理并发送异步请求
[NSURLConnection connectionWithRequest:request delegate:self];
}
# pragma mark--NSURLConnectionDataDelegate
//当接收到服务器响应的时候调用,该方法只会调用一次
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
//创建一个容器,用来接收服务器返回的数据
self.fileData = [NSMutableData data];
//获得当前要下载文件的总大小(通过响应头得到)
NSHTTPURLResponse *res = (NSHTTPURLResponse *)response;
self.totalLength = res.expectedContentLength;
NSLog(@"%zd",self.totalLength);
//拿到服务器端推荐的文件名称
self.fileName = res.suggestedFilename;
}
//当接收到服务器返回的数据时会调用
//该方法可能会被调用多次
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// NSLog(@"%s",__func__);
//拼接每次下载的数据
[self.fileData appendData:data];
//计算当前下载进度并刷新UI显示
self.currentLength = self.fileData.length;
NSLog(@"%f",1.0* self.currentLength/self.totalLength);
self.progressView.progress = 1.0* self.currentLength/self.totalLength;
}
//当网络请求结束之后调用
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//文件下载完毕把接受到的文件数据写入到沙盒中保存
//1.确定要保存文件的全路径
//caches文件夹路径
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *fullPath = [caches stringByAppendingPathComponent:self.fileName];
//2.写数据到文件中
[self.fileData writeToFile:fullPath atomically:YES];
NSLog(@"%@",fullPath);
}
//当请求失败的时候调用该方法
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(@"%s",__func__);
}