iOS开发之文件下载

来源:互联网 发布:云南网星大数据 编辑:程序博客网 时间:2024/05/04 12:44

本文将提供网络编程中文件的下载并存储在手机沙盒中的思路。

小文件下载:

如果文件比较小,下载方式会比较多:

直接用NSData的+ (id)dataWithContentsOfURL:(NSURL *)url;

利用NSURLConnection发送一个HTTP请求去下载

如果是下载图片,还可以利用SDWebImage框架

大文件下载:

如果大文件也用上面方法下载,由于文件很大,会有等待时间,又要将下载的文件存储到沙盒中,又要等待,这会给用户不好的体验。

我们仍然使用NSURLConnection的代理方法来下载文件。但是思路跟下载小文件是不一样的。如下:

- (void)viewDidLoad {    [super viewDidLoad];        // 获取资源路径url    NSURL *url = [NSURL URLWithString:KURLString];        // 创建请求    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];        // 建立连接,发送请求    [NSURLConnection connectionWithRequest:request delegate:self];}/** *  数据接受失败 */- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {    NSLog(@"失败");}/** *  接受服务器响应 */- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {    NSLog(@"刚接受响应");    // 获取沙盒路径    NSString *docuPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory , NSUserDomainMask, YES) lastObject];    // 拼接文件名    NSString *fileName = [docuPath stringByAppendingPathComponent:@"test.exe"];        // 创建文件管理器    NSFileManager *manage = [NSFileManager defaultManager];    // 创建目标文件路径(存储在沙盒中)    [manage createFileAtPath:fileName contents:nil attributes:nil];        // 创建写数据的文件句柄    self.writeHandle = [NSFileHandle fileHandleForWritingAtPath:fileName];        self.totalLength = response.expectedContentLength;    NSLog(@"完整文件大小:%lld", self.totalLength);}/** *  接受数据(会频繁调用) */- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {    NSLog(@"didReceiveData--%lu", data.length);            // 先将句柄移动到文件最尾部    [self.writeHandle seekToEndOfFile];    // 开始写数据    [self.writeHandle writeData:data];}/** *  数据接受完成 */- (void)connectionDidFinishLoading:(NSURLConnection *)connection {    NSLog(@"成功");    // 关闭文件句柄    [self.writeHandle closeFile];    self.writeHandle = nil;}


看看打印情况:

在沙盒中生成的文件:

当客户端开始接受服务器响应的时候,会调用didReceiveResponse:方法,在该方法中创建目标文件(和服务器上的文件类型相同),再创建文件句柄,用于写数据。

当有数据返回的时候,会调用didReceiveData:方法,在该方法中,将句柄移动到已写文件的尾部,再继续向文件中写数据。

当数据接受完的时候,会调用connectionDidFinishLoading:方法,在该方法中将句柄关闭,防止数据泄露。

将下载的大文件存储在Caches文件夹下,该文件夹下的文件不会被备份,也不会被随机删除。


下面给下载文件增加一个功能,就是暂停\开始下载功能,并实现下载进度。实现思路:暂停的时候调用记录已经文件的大小,再开始下载的时候就从当前文件尾部开始下载文件数据。代码如下:

- (IBAction)start {        if (self.isDownloading) {// 点击暂停        self.downloading = NO;        [self.downloadBtn setTitle:@"开始" forState:UIControlStateNormal];                // 取消下载操作        <span style="color:#FF0000;">[self.conn cancel];// conn已取消,该conn就不存在了,下次下载就必须重新创建        self.conn = nil;</span>            }else {// 点击开始        self.downloading = YES;        [self.downloadBtn setTitle:@"暂停" forState:UIControlStateNormal];                // 获取资源路径url        NSURL *url = [NSURL URLWithString:KURLString];                // 创建请求        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];                // 设置请求头,设置下载文件的起点        <span style="color:#FF0000;">NSString *value = [NSString stringWithFormat:@"bytes=%lld-", self.currentlength];        [request setValue:value forHTTPHeaderField:@"Range"];</span>                // 建立连接,发送请求        self.conn = [NSURLConnection connectionWithRequest:request delegate:self];    }    }/** *  数据接受失败 */- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {    NSLog(@"失败");}/** *  接受服务器响应 */- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {    // response.suggestedFilename:文件名    NSLog(@"刚接受响应:%@", response.suggestedFilename);        if (self.totalLength) return;// 判断是否是第一次下载        // 获取沙盒路径    NSString *docuPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory , NSUserDomainMask, YES) lastObject];    // 拼接文件名    NSString *fileName = [docuPath stringByAppendingPathComponent:response.suggestedFilename];        // 创建文件管理器   <span style="color:#FF0000;"> NSFileManager *manage = [NSFileManager defaultManager];</span>    // 创建目标文件路径(存储在沙盒中)    <span style="color:#FF0000;">[manage createFileAtPath:fileName contents:nil attributes:nil];</span>        // 创建写数据的文件句柄    <span style="color:#FF0000;">self.writeHandle = [NSFileHandle fileHandleForWritingAtPath:fileName];</span>        // 完整文件大小   <span style="color:#FF0000;"> self.totalLength = response.expectedContentLength;</span>    NSLog(@"完整文件大小:%lld", self.totalLength);}/** *  接受数据(会频繁调用) */- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {        NSLog(@"didReceiveData--%lu", data.length);    // 记录当前下载的文件大小    self.currentlength += data.length;        self.progressView.progress = (double)self.currentlength / self.totalLength;        // 先将句柄移动到文件最尾部    <span style="color:#FF0000;">[self.writeHandle seekToEndOfFile];</span>    // 开始写数据    <span style="color:#FF0000;">[self.writeHandle writeData:data];</span>}/** *  数据接受完成 */- (void)connectionDidFinishLoading:(NSURLConnection *)connection {    [self.downloadBtn setTitle:@"完成" forState:UIControlStateNormal];        // 重置数据    self.currentlength = 0;    self.totalLength = 0;        // 关闭文件句柄    <span style="color:#FF0000;">[self.writeHandle closeFile];</span>    <span style="color:#FF0000;">self.writeHandle = nil;</span>}

通过设置请求头Range可以指定每次从网路下载数据包的大小

Range示例:

bytes=0-499                      从0到499的头500个字节

bytes=500-999                 从500到999的第二个500字节

bytes=500-                        从500字节以后的所有字节

 

bytes=-500                        最后500个字节

bytes=500-599,800-899 同时指定几个范围

Range小结:

“-”用于分隔,前面的数字表示起始字节数;后面的数组表示截止字节数,没有表示到末尾。

“,”用于分组,可以一次指定多个Range,不过很少用

断点续传用到了NSFileManage和NSFileHandle两个类,一个负责创建文件,一个负责向文件中写数据。数据下载完之后,一定要将句柄关闭[self.writeHandle closeFile]

获取完整文件大小的方法不只这一种,如下:

1.    在didReceiveData:房中累加data的长度,最后就是完整文件的大小

2.    还可以利用response的真实类型NSHTTPURLResponse对象的allHeaderFields属性获取文件大小。



0 0