关于大文件下载

NSURLConnection下载

NSURLConnection还提供了另外一种发送请求的方式

// 发送请求去下载 (创建完conn对象后,会自动发起一个异步请求)
[NSURLConnection connectionWithRequest:request delegate:self];

这里用到了代理,那肯定要遵守协议了.遵守NSURLConnectionDataDelegate 协议.
进去看看有几个代理方法,其实我们能用到的也就三个。

/**
 *  请求失败时调用(请求超时、网络异常)
 *
 *  @param error      错误原因
 */
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{

}
/**
 *  1.接收到服务器的响应就会调用
 *
 *  @param response   响应
 */
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{

}
/**
 *  2.当接收到服务器返回的实体数据时调用(具体内容,这个方法可能会被调用多次)
 *
 *  @param data       这次返回的数据
 */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{

}
/**
 *  3.加载完毕后调用(服务器的数据已经完全返回后)
 */
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
}

通过执行下载操作,分别log上面三个方法,会发现didReceiveData这个方法会被频繁的调用,每次都会传回来一部分data,下面是官方api对这个方法的说明
is called with a single immutable NSData object to the delegate,
representing the next portion of the data loaded from the connection. This is the only guaranteed for the delegate to receive the data from the resource load.

由此我们可以知道,这种下载方式是通过这个代理方法每次传回来一部分文件,最终我们把每次传回来的数据合并成一个我们需要的文件。

这时候我们通常想到的方法是定义一个全局的NSMutableData,接受到响应的时候初始化这个MutableData,在didReceiveData方法里面去拼接
[self.totalData appendData:data];
最后在完成下载的方法里面吧整个MutableData写入沙盒。
代码如下:

@property (weak, nonatomic) IBOutlet UIProgressView *myPregress;

@property (nonatomic,strong) NSMutableData* fileData;

/**
 *  文件的总长度
 */
@property (nonatomic, assign) long long totalLength;

/**
 *  1.接收到服务器的响应就会调用
 *
 *  @param response   响应
 */
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    self.fileData = [NSMutableData data];
    // 获取要下载的文件的大小
    self.totalLength = response.expectedContentLength;
}
/**
 *  2.当接收到服务器返回的实体数据时调用(具体内容,这个方法可能会被调用多次)
 *
 *  @param data       这次返回的数据
 */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    [self.fileData appendData:data];
    self.myPregress.progress = (double)self.fileData.length / self.totalLength;
}
/**
 *  3.加载完毕后调用(服务器的数据已经完全返回后)
 */
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    // 拼接文件路径
    NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    NSString *file = [cache stringByAppendingPathComponent:response.suggestedFilename];

    // 写到沙盒中
    [self.fileData writeToFile:file atomically:YES];
}
我这里下载的是javajdk。(度娘的地址)
注意:通常大文件下载是需要给用户展示下载进度的。
这个数值是 已经下载的数据大小/要下载的文件总大小
已经下载的数据我们可以记录,要下载的文件总大小在服务器返回的响应头里面可以拿到,在接受到响应的方法里执行
 NSHTTPURLResponse *res = (NSHTTPURLResponse*)response;

NSDictionary *headerDic = res.allHeaderFields;
NSLog(@"%@",headerDic);
self.fileLength = [[headerDic objectForKey:@"Content-Length"] intValue];

不得不说苹果太为开发者考虑了,我们不必这么麻烦的去获取文件总大小了,
response.expectedContentLength 这句代码就搞定了。
response.suggestedFilename 这句代表获取下载的文件名

 这样我们确实可以下载文件,最后拿到的文件也能正常运行

但是有个致命的问题,内存!用来接受文件的NSMutableData一直都在内存中,会随着文件的下载一直变大,

所有这种处理方式绝对是不合理的。

合理的方式在我们获取一部分data的时候就写入沙盒中,然后释放内存中的data。

这里要用到NSFilehandle这个类,这个类可以实现对文件的读取、写入、更新。
下面总结了一些常用的NSFileHandle的方法,在这个表中,fh是一个NSFileHandle对象,data是一个NSData对象,path是一个NSString 对象,offset是易额Unsigned long long变量。

 

具体关于NSFileHandle的用法各位自行搜索。

在接受到响应的时候就在沙盒中创建一个空的文件,然后每次接收到数据的时候就拼接到这个文件的最后面,通过- (unsigned long long)seekToEndOfFile; 这个方法

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    // 文件路径
    NSString* ceches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    NSString* filepath = [ceches stringByAppendingPathComponent:response.suggestedFilename];

    // 创建一个空的文件到沙盒中
    NSFileManager* mgr = [NSFileManager defaultManager];
    [mgr createFileAtPath:filepath contents:nil attributes:nil];

    // 创建一个用来写数据的文件句柄对象
    self.writeHandle = [NSFileHandle fileHandleForWritingAtPath:filepath];

    // 获得文件的总大小
    self.totalLength = response.expectedContentLength;

}
/**
 *  2.当接收到服务器返回的实体数据时调用(具体内容,这个方法可能会被调用多次)
 *
 *  @param data       这次返回的数据
 */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    // 移动到文件的最后面
    [self.writeHandle seekToEndOfFile];

    // 将数据写入沙盒
    [self.writeHandle writeData:data];

    // 累计写入文件的长度
    self.currentLength += data.length;

    // 下载进度
    self.myPregress.progress = (double)self.currentLength / self.totalLength;
}
/**
 *  3.加载完毕后调用(服务器的数据已经完全返回后)
 */
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    self.currentLength = 0;
    self.totalLength = 0;

    // 关闭文件
    [self.writeHandle closeFile];
    self.writeHandle = nil;
}

这样在下载过程中内存就会一直很稳定了,并且下载的文件也是没问题的。

原文:http://www.90159.com/2016/01/07/ios-big-file-download/

 
posted @ 2016-09-01 10:38  光光96  阅读(252)  评论(0编辑  收藏  举报