NSURLConnection 网络下载,是iOS2.0就开始提供的API,一直到iOS9的时候被弃用。
+ (void)sendAsynchronousRequest:(NSURLRequest*) request queue:(NSOperationQueue*) queue completionHandler:(void (^)(NSURLResponse* _Nullable response, NSData* _Nullable data, NSError* _Nullable connectionError)) 这个异步下载的方式是在iOS5才有的,在此之前是没有这个方法的。而且,使用这个方法你无法获取到下载进度,并且当下载完成后才能对数据进行操作。特别是在大数据视频类的下载任务时,占用内存过高,可能就会崩掉。在开发简单的下载任务,并且文件不大的时候还是没有问题的,并且比较方便。
问题来了,那么开发复杂的网络请求,或者文件较大时,怎么用NSURLConnection来做呢?!答案是:NSURLConnectionDataDelegate<代理>。记住,不要和NSURLConnectionDownloadDelegate搞混淆了。NSURLConnectionDownloadDelegate是用于杂志下载的,iOS中有一个系统自带的应用就是杂志。NSURLConnectionDownloadDelegate可以监听下载进度,但是下载完毕后的文件你是拿不到的。
通过代理来下载,直接看代码:
NSString *str = @"https://github.com/chunnilzp/StudyCoreAnimation/archive/master.zip";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:str]];
NSURLConnection *con = [NSURLConnection connectionWithRequest:request delegate:self];
[con start];
下面我们来看看NSURLConnectionDataDelegate的几个代理方法:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
1.接收到服务器的响应,做接收文件前的准备工作。
NSURLResponse中包含了下载文件的信息:
expectedContentLength 文件大小
suggestedFilename 文件名称
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
2.接收到服务器的数据,可能被调用很多次
下载并不是一次性把数据全部给你,而是一段一段的下载。所以可能被调用很多次。
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
3.下载完毕后的一个通知
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
4.下载失败或错误是的一个通知
那么,现在开始解决刚才说的问题:
1.进度条。
定义一个long long的全局变量expectedContentLength,一个long long的全局变量dataSize,一个文件保存路径的变量filePath
在didReceiveResponse中获取文件大小,初始化dataSize,并生成下载文件的保存路径
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
self.expectedContentLength = response.expectedContentLength;
self.dataSize = 0;
self.filePath = [NSString stringWithFormat:@"***************/%@", response.suggestedFilename];//具体保存在哪,看需求了
}
在didReceiveData中计算下载进度
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
self.dataSize += [data length];
float progress = (float)self.dataSize/self.expectedContentLength;
NSLog(@"当前的进度:%f", progress);
}
2.实时把文件写入磁盘,减少内存消耗,这里有2种方式
A.使用NSFileHandle文件句柄,主要功能是对同一个文件进行二进制的读写!
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
self.dataSize += [data length];
float progress = (float)self.dataSize/self.expectedContentLength;
NSLog(@"当前的进度:%f", progress);
NSFileHandle *fp = [NSFileHandle fileHandleForWritingAtPath:self.filePath];
//判断文件是否存在,如果文件不存在,先把文件写入磁盘
if (fp == nil) {
[data writeToFile:self.filePath atomically:YES];
}else{
//将文件指针移动到文件的末尾
[fp seekToEndOfFile];
//在文件指针的地方写入文件
[fp writeData:data];
//在C语言的开发中,凡是涉及到文件的读写,都会有打开和关闭的操作
[fp closeFile];
}
}
B.NSOutputStream 输出流的形式写入数据,这个可以理解为水瓶,需要操作时,先打开瓶盖,然后添加水(不需要指定在哪里添加,直接当进去就OK),操作完以后盖起来。看代码吧,一个语言能力非常有限的码农。。望理解!
定义NSOutputStream全局变量stream
在didReceiveResponse中初始化stream,并打开盖子!!!
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
self.expectedContentLength = response.expectedContentLength;
self.dataSize = 0;
self.filePath = [NSString stringWithFormat:@"/Users/lizeping/Desktop/测试数据/%@", response.suggestedFilename];
self.stream = [[NSOutputStream alloc] initToFileAtPath:self.filePath append:YES];
[self.stream open];
}
//往水瓶中倒水,并告知水有多少
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
//计算下载进度
self.dataSize += [data length];
float progress = (float)self.dataSize/self.expectedContentLength;
NSLog(@"当前的进度:%f", progress);
[self.stream write:data.bytes maxLength:data.length];
}
盖上盖子!
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
[self.stream close];
}
3步解决,很简单的处理方式!!!
2种解决方式都可以,看个人喜号自取自用。
下面是重点,说到下载操作,我们都会想到多线程,耗时操作我们不可能丢在主线程,上面的所有东西都是在主线程中执行的。那么,我们如何用子线程去处理,不是简单的把初始化下载任务丢到子线程就OK了。因为是网络事件,可能会有响应延迟,那么子线程跑完就释放了,那么下载的操都不会执行。所以,我们需要开启RunLoop。
会用到CoreFoundation 框架 CFRunLoopRef ,定义全局变量CFRunLoopRef 的downLoadRunLoop。开始线程初始化下载任务的同时NSURLConnection调用setDelegateQueue,让NSURLConnection的所有代理都在子线程中进行(就是上面那4个代理方法全是在子线程中跑)。
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSString *str = @"https://github.com/chunnilzp/StudyCoreAnimation/archive/master.zip";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:str]];
NSURLConnection *con = [NSURLConnection connectionWithRequest:request delegate:self];
[con setDelegateQueue:[[NSOperationQueue alloc] init]];
[con start];
self.downLoadRunLoop = CFRunLoopGetCurrent();
CFRunLoopRun();
});
这样就可以在子线程中下载了。
当然你启动了Runloop。在执行完后就需要关闭,不然线程无法释放,消耗会很大。
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
[self.stream close];
CFRunLoopStop(self.downLoadRunLoop);
}
代码在这里。明后天是NSURLSession。
很多东西一直在用,特别是经常用的第三方,但是具体里面怎么实现的,用的哪些东西还需要好好了解。