您的位置:首页 > 其它

使用NSURLConnection解决下载的问题——同步解决下载内存峰值与进度跟进的问题(只需了解原理)

2015-10-30 09:27 357 查看
下载中涉及到的问题:

问题一:直接使用Connection块操作进行下载,不能实现进度的跟进。——导致用户体验不好。

解决方法:通过代理的方式来处理网络数据。

问题二:当用代理的方式进行处理网络数据,如果每传过来一个数据包就添加在全局数据Data的后面 最后全部下载完成后才把连接后的NSData全局数据写入到指定路径,这样做会存在峰值问题。因为全部都添加在一起都在内存中,导致下载完成后的某一时刻内存占用很大。

解决方法:利用句柄NSFileHandle进行逐步写入,即每下载一点就写进文件,然后把句柄移到文件末尾,继续写入。

注意:如果不使用句柄的话,后面写进去的数据会把前面数据给覆盖掉。如果使用句柄,每次写入都会在句柄之后写入数据。句柄的创建方法:+ (instancetype)fileHandleForWritingAtPath:(NSString
*)path;但是必须注意的是——如果指定路径path所在的文件不存在的话,那么创建的句柄对象就是nil,所以需要先判断句柄是否为nil,如果为nil的话就要用writeToFile方法写入一次先生成文件,其后每次写入数据的时候都使用句柄。具体方法如下:

// 如果文件不存在,句柄就是nil。这时候就无法操作文件
if (fp ==nil) {
[data
writeToFile:self.targetPathatomically:YES];
}else
{
//将句柄移到当前文件的末尾
[fp seekToEndOfFile];
//将数据写入(是以句柄作参照来开始写入的)
[fp writeData:data];
//在C语言中,所有的文件操作完成以后,都需要关闭文件,这里也需要关闭。
//为了保证文件的安全
[fp closeFile];
}

实现跟踪进度下载需要让当前控制器遵守NSURLConnectionDataDelegate协议,并实现内部的四个代理方法如下:

代理方法(一)当客户端接收到服务器响应后首先调用的方法:

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse
*)response;
代理方法(二)当客户端接受响应后会不断接服务器发送来的数据,会不断地调用didReceiveData方法:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData
*)data;
代理方法(三)当所有的数据传输完毕后会调用connectionDidFinishLoading方法:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
代理方法(四)如果下载过程中出现了错误,就会调用didFailWithError方法:

-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError
*)error;
调用过程概述:

客户端发送一个请求到服务器,服务器接收到请求后返回一个响应。在第一个代理方法didReceiveResponse中就会接收到服务器端的响应信息,服务器发给客户端的响应包含一个响应头(响应头里包含了文件的大小)。
然后服务器将整个文件打包成很多小的二进制数据包,不断地调用第二个代理方法进行发送(每发送一个数据包都会调用一次didReceiveData代理方法)。当所有的数据包都传输完毕后就会调用connectionDidFinishLoading这个方法。如果在下载的过程中出现了错误,就会调用didFailWithError这个方法。
代理中需要执行的操作如下:

一般在第一个代理方法中可以实现获取下载文件的总长度与服务器建议的文件名、将进度清零、设置下载文件后的保存路径、删除保存路径的同名同类型的文件的操作。
一般在第二个代理方法中可以实现累加记录当前文件的长度并计算进度、利用文件管理类NSFIleManager和句柄操作类NSFileHandle进行文件的逐步写入等操作。
一般在第三个代理方法中实现给用户下载完成的提示也可以发送通知。
一般在第四个代理方法中实现后出现错误后的处理步骤。
首先打开本地服务器(自己电脑的后台服务器),并拖进服务器端文件夹一个视频文件。打开服务器验证如下:



然后打开本地服务器。

具体代码如下:

<span style="font-size:18px;"><span style="font-size:18px;">//
//  ViewController.m
//  使用NSURLConnection进行下载
//
//  Created by apple on 15/10/29.
//  Copyright (c) 2015年 LiuXun. All rights reserved.
//
/**
IOS5.0之前, 网络的下载是一个黑暗的时代
但是,需要了解思路

NSURLConnection下载存在的问题,IOS2.0就有了。专门负责网络的传输,已经有10多年的历史了。
特点:
- 处理简单的网络操作,非常简单。
- 但是处理复杂的网络操作非常繁琐。

ASI和AFN第三方框架才会出现。
*** IOS5.0以前通过代理的方式处理网络的数据

存在的问题:
1. 下载的过程中,没有进度的跟进—— 导致用户体验不好
2.  存在内存的峰值

解决进度跟进的问题:
解决的办法:通过代理的方式来处理网络数据

代理还是出现内存峰值,是因为全部接受了,再去写入
解决办法:接受到一点写一点。
*/

#import "ViewController.h"
#import <Foundation/Foundation.h>
/**
NSURLConnectionDownloadDelegate :此代理协议只适用于杂志的下载,国内很少有人开发。
*/
@interface ViewController ()<NSURLConnectionDataDelegate>

// 下载文件的总长度
@property(nonatomic, assign) long long expectedContentLength;

// 当前下载的长度
@property(nonatomic, assign) long long currentLength;

// 保存文件的目标路径
@property(nonatomic, strong)NSString *targetPath;

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// 下载使用GET请求

// 1. url
NSString *urlStr = @"http://127.0.0.1/07-知识点勘误.mp4";
// 因为网址中含有中文,所以要进行转义——使用stringByAddingPercentEscapesUsingEncoding方法
urlStr  =[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

NSURL *url = [NSURL URLWithString:urlStr];

// 2. 请求
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:1 timeoutInterval:2.0f];

// 3. 连接
//    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
//
//       // 把数据保存到桌面
//        [data writeToFile:@"/Users/apple/Desktop/123.mp4" atomically:YES];
//
//        NSLog(@"下载完成");
//    }];

// 3 、创建连接
NSURLConnection *connect = [NSURLConnection connectionWithRequest:request delegate:self];

// 4、启动网络连接
[connect start];
}

#pragma mark- 实现方法

// 1、接受到服务器的响应— 做好准备
/**
接收到服务器响应后首先调用这个方法

NSURLResponse  响应
- (instancetype)initWithURL:(NSURL *)URL MIMEType:(NSString *)MIMEType expectedContentLength:(NSInteger)length textEncodingName:(NSString *)name;

URL:资源路径
MIMEType(Content-Type):返回的二进制数据类型
expectedContentLength:预期的文件长度,对于下载来说就是文件的大小
textEncodingName:文本的编码名称
suggestedFileName:服务器建议的文件名

UTF-8 :几乎涵盖了全世界200多个国家的语言文字
GB2312:国内的一些老的网站可能还在使用这个编码  包含了6700多个汉字
*/
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
//    NSLog(@"%@", response);

// 文件的大小
NSLog(@"%lld", response.expectedContentLength);

// 记录文件的总长度
self.expectedContentLength = response.expectedContentLength;

// 将下载的长度清零
self.currentLength = 0;

// 设置文件的目标路径
self.targetPath = [@"/Users/apple/Desktop"  stringByAppendingPathComponent: response.suggestedFilename];

// 简单粗暴,准备接收文件数据之前,直接删掉
[[NSFileManager  defaultManager] removeItemAtPath:self.targetPath error:NULL];
}

// 2、接收到服务器返回的数据— 会被调用多次
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSLog(@"接收到数据 长度为%tu, 可以拼接所有数据", data.length);

// 记录当前已经下载文件的长度
self.currentLength += data.length;

// 计算进度
float progress = (float)self.currentLength/self.expectedContentLength;
NSLog(@"进度%f", progress);

// 拼接数据
/*
NSFileManager —> 做文件获取文件夹的复制和删除以及路径文件夹的操作

NSFileHandle —> 文件写入句柄 操作文件
*/
//     建立文件句柄, 准备写入到targetPath
NSFileHandle *fp = [NSFileHandle fileHandleForWritingAtPath:self.targetPath];

//  如果文件不存在,句柄就是nil。这时候就无法操作文件
if (fp == nil) {
[data writeToFile:self.targetPath atomically:YES];
}else
{
// 将句柄移到当前文件的末尾
[fp seekToEndOfFile];

// 将数据写入(是以句柄作参照来开始写入的)
[fp writeData:data];

// 在C语言中,所有的文件操作完成以后,都需要关闭文件,这里也需要关闭。
// 为了保证文件的安全
[fp closeFile];
}

}

// 3、所有的数据传输完毕
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"所有的数据传输完毕,写成文件");
}

// 4、下载过程中出现错误后会调用这个方法
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(@"注意网络请求的过程中,一定要注意错误处理");
}
@end

/**
客户端发送一个请求到服务器,服务器接收到请求后返回一个响应。在第一个代理方法didReceiveResponse中就会接收到服务器端的响应信息,服务器发给客户端的响应包含一个响应头 (响应头里包含了文件的大小)。
然后服务器将整个文件打包成很多小的二进制数据包,不断地调用第二个代理方法进行发送(每发送一个数据包都会调用一次didReceiveData代理方法)。当所有的数据包都传输完毕后就会调用connectionDidFinishLoading这个方法。如果在下载的过程中出现了错误,就会调用didFailWithError这个方法。

*/</span></span>
运行完成后,指定的桌面路径发现了对应的文件。控制台输出如下:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: